Relationship hasManyThrough in Laravel? - laravel

I have three models: User, Category, Announcement.
User model:
public function categories()
{
return $this->belongsToMany("App\Category");
}
public function announcements()
{
return $this->hasManyThrough('App\Announcement', 'App\Category');
}
Category model:
public function announcements()
{
return $this->belongsToMany('App\Announcement');
}
Announcement model:
public function categories()
{
return $this->belongsToMany('App\Category');
}
I need to select all announcements through user_categories table.
For this I do:
$res = User::where("id", 1)->with("announcements")->get();
dd($res);
In result I get SQL error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'announcements.category_id' in 'on clause' (SQL: select `announcements`.*, `categories`.`user_id` from `announcements` inner join `categories` on `categories`.`id` = `announcements`.`category_id` where `categories`.`user_id` in (1) and `announcements`.`deleted_at` is null)
Why Laravel does try to search ``announcements.category_id` in table?
In conclusion I need to get all announcements on categories which user subscribed.
Table Database structure:
Users
id | name
Announcements
id | name
categories
category_id | announcement_id
users_categories
category_id | user_id
So, I need to get all announcements through table users_categories where there are relation this table with categories that is belong to announcement table.

You cannot eager load this relationship because
To perform this query, Eloquent inspects the foreign key on the intermediate table.
Thus the error
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'announcements.category_id'
Because HasManyThrough() looks for the relationship in the specified model table.
I would suggest you look at this thread:
https://laravel.io/forum/05-21-2014-problem-with-eloquents-hasmanythroughs-query
They show you how to write your own query to do that. An example would be, in the user model do
public function getAnnouncements() {
$announcements = [];
foreach( $this->categories as $category ) {
foreach( $category->announcements as $annoucement ) {
if ( !isset($announcements[$annoucement->id]) ) { //avoid duplicates
$annoucements[$announcement->id] = $announcement;
}
}
}
}
The nested foreach will give all announcement belonging to a category that related to a user.
Improving the function
public function getAnnouncements() {
$announcements = new Collection();
foreach( $this->categories as $category ) {
$announcements->collect($category->announcements);
}
}

Related

Laravel - for some reason, max breaks my query

I have this piece of code:
$query = Student::whereHas('statusuri', function($q) use ($status) {
$q->latest('status_student.id')->take(1)
->where('status_id', $status)
->whereNotNull('status_id');
});
and it works fine, but I don't necessarily get the desired result.
I tried changing the first line to max (so I don't filter all my records then do a limit 1), I just get the highest ID from the beginning - like this:
$query = Student::whereHas('statusuri', function($q) use ($status) {
$q->max('status_student.id')
->where('status_id', $status)
->whereNotNull('status_id');
});
but then my query breaks.
For some reason, I get this:
Unknown column 'students.id' in 'where clause' (SQL: select max(`status_student`.`id`) as aggregate from `statusuri` inner join `status_student` on `statusuri`.`id` = `status_student`.`status_id` where `students`.`id` = `status_student`.`student_id`)
Why does my query break after I do this change?
Thanks.
Tables:
students
id bigint(20)
//other non-related data
statusuri
id bigint(20)
nume VARCHAR(255)
status_student
id int(11)
student_id int(10)
status_id int(10)
stare_stagiu_id int(11)
created_at timestamp
updated_at timestamp
statusuri() from Student
public function statusuri()
{
return $this->belongsToMany(Status::class, 'status_student')
->withPivot('id', 'data_inceput', 'data_sfarsit', 'document', 'status_id', 'stare_stagiu_id')
->withTimestamps();
}
Status and StatusStudent classes
class StatusStudent extends Model
{
protected $table = 'status_student';
protected $fillable = ['id', 'student_id', 'status_id', 'stare_stagiu_id'];
}
class Status extends Model
{
protected $table = 'statusuri';
public $fillable = ['nume'];
}
Your relations are messed up. The query is trying to use a column from the student table, but the student table is not available in said query as it is not joined. See this fiddle to see what goes wrong in the SQL.
http://sqlfiddle.com/#!9/52c96fa/6
In the end, I'd do it like this if I understood you correctly:
In StatusStudent.php (Model):
public function student() {
return $this->hasOne(Student::class, 'id', 'student_id');
}
In Controller:
public function stackoverflowtest() {
//Set teststatus
$status = 1;
//Get the latest status of all users - and if that status is correct, retrieve into array
$latest = DB::select( DB::raw("SELECT max(id) as id, student_id FROM status_student group by student_id"));
$array = [];
foreach ($latest as $l) {
$status_id = StatusStudent::whereId($l->id)->whereStatusId($status)->first();
if ($status_id) {
array_push($array, $status_id);
}
}
//$array now holds all the StatusStudent, simply user ->student to get the student related to said status, example below
if($array) {
dd($array[0]->student);
return $array;
} else {
return 'No match';
}
}
First, we get all latest records for each user IF the status is correct. Then, we simply get the Student from the status_student table via the relation.

In Laravel 7 How to orderBy Roles->name?

I have User Model with Roles relationships like so :
public function users(){
return $this->belongsToMany(User::class)->withTimestamps();
}
And Role Model with user relationships :
public function roles(){
return $this->belongsToMany(Role::class)->withTimestamps();
}
And of course Pivot Table "role_user" contains id, user_id, role_id and timestamps
I try to get users ordered By roles name like so :
$users = App\Models\User::with('roles')->orderBy('roles.name', 'desc')->get();
but i have this error :
Illuminate\Database\QueryException
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'roles.name' in 'order clause' (SQL: select * from `users` where `users`.`deleted_at` is null order by `roles`.`name` desc)
http://localhost:8000/users
Please, some helps .
Try Like this:
$users = App\Models\User::with('roles')->get();
// order of the roles
$array = ['User','Admin','Doctor', 'Manager'];
$sorted = $users->sortBy(function($model) use ($array) {
return array_search($model['roles'], $array);
});
Hope it will help you!

How can I access model Relation with where condition of child and parent table in laravel?

I have Games table and which has the following schema
id | status | name
status column has 2 values (Active, Pending)
And GamePlayer table which has the following schema
id | game_id | player_id | request_status
request_status column has 3 values (Pending, Confirm, Rejected)
Now I have to select all game in which the player is involved but with the following constraints:
If the game is Pending state then it will be shown to all game_players
If the game is in Active state then it will be only shown to the game_player whose request_status is Confirm.
Game(Model)
public function GamePlayer()
{
return $this->hasMany('App\Models\GamePlayer', 'game_id', 'id');
}
public function getGames($playerId)
{
$gameList = Game::with(['GamePlayer','Category:id,name'])
->whereHas('GamePlayer', function ($q) use ($playerId) {
$q->where('player_id', $playerId);
})->get();
return $gameList;
}
Controller
$this->gameObj = new Game();
$gameList = $this->gameObj->getGames($player_id);
Please help me out how can I populate data from another table based on condition(parent table as well as the child).
You can use condition in your relationship to get only confirmed game players
public function activeGamePlayers()
{
return $this->hasMany('App\Models\GamePlayer', 'game_id', 'id')
->where('request_status', 'confirm');
}
You can as well use scopes (https://laravel.com/docs/7.x/eloquent#local-scopes) to select only active games
public function scopeActive($query)
{
return $query->where('status', 'active');
// You can then use $game->active()->...
}
So your getGame would look like:
public function getGames($playerId)
{
$player = GamePlayer::find($playerId);
if ($player->status == 'active') {
$gameList = Game::with(['GamePlayer', 'Category:id,name'])
->get();
} else {
$gameList = Game::with(['GamePlayer', 'Category:id,name'])
->active()->get();
}
return $gameList;
}
NOTE:
in your specific case, I would instead get started from the GamePlayer Model to get the games, instead of coming from the Game Model - you can use scopes and conditions in relationships as well to make your code more readable.
You can use Laravel method whereHas. You should read this part of Laravel the documentation.
Note that you should have relationships declared on every model.

Laravel: How to get data from 3 tables with relationship

I have 3 Tables:
Customers
id
name
Sales
customer_id
sale_date
Contacts
customer_id
contact_date
There aren't any update operations in the contacts table. Each process opens a new record in the contacts table. So, a user can have more than one records in the contacts table.
Here are my relations in models:
Customer
public function contacts()
{
return $this->hasMany(Contact::class);
}
public function sales()
{
return $this->hasMany(Sale::class);
}
Contact
public function customer()
{
return $this->belongsTo('App\Customer', 'customer_id');
}
Sale
public function customer()
{
return $this->belongsTo('App\Customer');
}
I would like to have the latest record of the contacts table and make it join with the other related tables.
Here is the query which I have tried:
$record = Contact::groupBy('customer_id')
->select(DB::raw('max(id)'));
$result = Customer::query();
$result->where('is_active', 'YES');
$result->with('sales');
$result->whereHas('contacts', function ($q) use($record){
return $q->whereIn('id', $record)->where('result', 'UNCALLED');
});
return $result->get();
In the blade file, I get some result in foreach loops. However, I am unable to get the related data from the sales and contacts table.
#foreach($result as $item)
#foreach($item->sales as $sale) // Has no output and gives error: Invalid argument supplied for foreach()
#foreach($item->contacts as $contact) // Has no output and gives error: Invalid argument supplied for foreach()
Can anyone help me how to display the sale and contact date? Or any idea for how to improve this code quality?
If you want the latest record of the contacts you can declare another relationship on the Customer model, e.g.:
public function latest_contact()
{
return $this->hasOne(Contact::class)->latest('contact_date');
}
BTW you can always declare one or more hasOne additional relationship if you have a hasMany in place the foreign key used is the same.
In this way you can retrieve latest_contact eager loaded with your Customer model:
$customer = Customer::with('latest_contact')->find($id);
Or use this relationship in your queries, something like that:
$customers = Customer::where('is_active', 'YES')
->with('sales')
->with('contacts')
->whereHas('last_contact', function ($q){
return $q->where('result', 'UNCALLED');
})->get();
Or that:
$customers = Customer::where('is_active', 'YES')
->with('sales')
->with('contacts')
->with('last_contact', function ($q){
return $q->where('result', 'UNCALLED');
})->get();
If you want you can declare last_contact with the additional where:
public function latest_contact()
{
return $this->hasOne(Contact::class)
->where('result', 'UNCALLED')
->latest('contact_date');
}
This way all other queries should be easier.
I hope this can help you.
I'm not sure, but can you try to do the following:
return Customer::where('is_active', 'YES')
->with([
'sale',
'contact' => function ($query) use($record) {
return $query->whereIn('id', $record)->where('result', 'UNCALLED');
}
])->get();

Laravel Fetching one to Many Relationship

Hello I am learning laravel and I am having an issue retrieving data from my relations.
In my database there are Product and Groups filled with dummy data.
I defined my relationship like this in product model:
public function Group()
{
return $this->hasMany('App\Groups','product_id', 'id');
}
And in my group vice versa with :
public function Product()
{
return $this->belongsTo('App\Product','product_id', 'id');
}
The way I am referencing to my products table is :
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
Now I have column product_id in my database under groups, and it is linked to if from products id it seems.
The groups table contains of its auto incremented id and product_id foreign key column.
While products table has auto incremented id and name column.
The issue is here :
How do I return the products that are not null or have value (of products id) in groups table.
I tried something like this in my filter controller:
public function getProductsWithGroup()
{
$Products = Product::with('groups')->get();
return $Products ;
}
But that is giving me call to undefined relations.
I am not sure how to access belongsTo or hasMany methods and whether I need an extra group_id column in my products table.
You named the relationship wrong. It should be groups & define in lowercase as
public function groups()
{
return $this->hasMany('App\Groups','product_id', 'id');
}
And use ->has() to check existence
public function getProductsWithGroup()
{
$Products = Product::has('groups')->get();
return $Products ;
}
->with() is used to eager load and ->has() is used to check existence & filter.
To get the products don't have any groups,
$Products = Product::doesntHave('groups')->get();
To see other ways to use ->has() check, https://laravel.com/docs/5.7/eloquent-relationships#querying-relationship-existence

Resources