Finding all specific relations based on another pivot relation - laravel

I know the title of my question is really weird! I hope I can clarify it with explanations. I have three models, Book, Auther, and AutherType. My tables structure :
table books:
id
title
1
book1
2
book2
table authors:
id
name
1
Bob
2
Sara
table author_types:
id
title
1
writer
2
translator
Each Book can have multiple Authors with a type. The pivot table of this relationship is something like that:
book_id
author_id
author_type_id
1
1
1
1
2
2
2
2
1
Also, I have authors(general relation) and writers(partial relation) relationship in my Book model:
public function authors()
{
return $this->belongsToMany('App\Models\Author', 'author_book', 'book_id')->withPivot('author_type_id');
}
public function writers()
{
return $this->belongsToMany('App\Models\Author', 'author_book', 'book_id')
->wherHas('authorType', function($q){
$q->where('title', 'writer');
})
->withPivot('author_type_id');
}
My question is that, how can I fetch all specific type of a book? For example, all writers of a book. In this case, the writer of the book1 is Bob. But my writers relation cannot satisfy this. The output of $book->writers for the book1 contains Bob and Sara. Because Sara has writer role for another book!

While there is no ability to write queries on complex pivots (belongsToMany relations), we have two choices:
First(which is the simplest one): get the writer type id and pass it through the relation, like:
public function writers()
{
$writer_id = AuthorType::where('title', 'writer')->first()->id;
return $this->authors()->wherePivot('author_type_id', $writer_id);
}
Second (The heavy choice), write a new method in Author model that gives the author types for a specific book and then in Book model filter authors according there types for this book:
in Author.php:
public function authorTypes()
{
return $this->belongsToMany('App\Models\AuthorType', 'author_book');
}
public function authorTypesForBook($book_id){
return $this->authorTypes()->wherePivot('book_id', '=' , $book_id);
}
and in Book.php:
public function writers()
{
$authors = $this->belongsToMany('App\Models\Author', 'author_book')->withPivot('author_type_id')->get();
$book_id = $this->id;
return $authors->filter(function ($item) use ($book_id){
return $item->authorTypesForBook($book_id)->get()->contains(function($value){
return $value->title == 'writer';
});
});
}
Hope this would help.

Related

How to convert this SQL statement to laravel code?

So I have two tables Animals and Adoptions
The animal table holds [animalID, name, DOB]
Adoptions hold [id, animalID, userID, approved]
I need to write a statement which would match the animalID in both the tables and would return the name and DOB of the animal where approved is 0.
I have hardly any previous experience with either mySQL or Laravel so my assumption would be that the SQL query would look like this:
SELECT animalName, DOB
FROM animal
INNER JOIN
Adoption ON adoption.animalID = animal.animalID
WHERE
Adoption.approved = 0
Currently I am returning all of the animals in the table. So I need to add the join and do the checking to see if the animalID in the adoptions table has a value of 0
public function available ()
{
//JOIN ANIMAL AND ADOPTION TABLES
//RETURN ALL ANIMALS WHERE APPROVED = 0
$animalQuery = Animal::all();
return view('/available', array('animals'=>$animalQuery));
}
public function available ()
{
//JOIN ANIMAL AND ADOPTION TABLES
//RETURN ALL ANIMALS WHERE APPROVED = 0
$animalQuery = Animal::select('animals.id','animals.name', 'animals.DOB')
->join('adoptions', 'adoptions.animalid', '=', 'animals.id')
->where('adoptions.approved', 0)
->get();
return view('/available', array('animals'=>$animalQuery));
}
This is my answer which worked for me
First of all, you would need two models. An Animal model and an Adoption model. Laravel uses models to access your database table.
Using the Animal model you could create a query like this:
\App\Animal::with('adoption')->select('name','dob')->get();
Then inside your Animal model, you would need to create the relationship between the animal and adoption:
public function adoption()
{
return $this->hasOne('App\Animal', 'animalID', 'animalID');
}
This would be one way to get an animal, with the adoption information.
I really recommend reading through the Laravel documentation for relationships and eloquent, and possibly read up on some SQL.
https://laravel.com/docs/5.8/eloquent
https://laravel.com/docs/5.8/eloquent-relationships
Assuming you have an Adoption model, and adoption relation on your Animal model, you can do the following:
$animals = Animal::select('animalName`, `DOB`)
->with(['adoption' => function($query) {
$query->where('approved', false);
})->get();

Laravel - Eloquent relation whereHas one or more other relations

I'm learning Laravel and Laravel eloquent at the moment and now I try to solve a problem using relations in Laravel.
This is what I want to archive:
The database holds many sport clubs. A sport club has a lot of teams. Each team has games. The teams table has a column named club_id. Now I want to create Eloquent relations to get all games of a club.
Here is what I got so far:
Club model
id => PRIMARY
public function games()
{
return $this->hasMany('App\Models\Games')->whereHas('homeTeam')->orWhereHas('guestTeam');
}
Game model
home_id => FOREIGN KEY of team ; guest_id => FOREIGN KEY of team
public function homeTeam()
{
return $this->belongsTo('App\Models\Team','home_id')->where('club_id','=', $club_id);
}
public function guestTeam()
{
return $this->belongsTo('App\Models\Team','guest_id')->where('club_id','=', $club_id);
}
Team model
id => PRIMARY ; club_id => FOREIGN
In my controller all I want to do is Club::findOrFail($id)->games()
Executing the code above returns a SQL error that the games table does not have a column named club_id.
What is the correct way to create this kind of relation?
Thanks!
EDIT
Thanks to Nikola Gavric I've found a way to get all Games - but only where club teams are the home or away team.
Here is the relation:
public function games()
{
return $this->hasManyThrough('App\Models\Game','App\Models\Team','club_id','home_id');
}
How is it possible to get the games where the home_id OR the guest_id matches a team of the club? The last parameter in this function does not allow an array.
There is method to retrieve a "distant relationship with an intermediary" and it is called Has Many Through.
There is also a concrete example on how to use it which includes Post, Country and User, but I think it will be sufficient to give you a hint on how to create games relationship inside of a Club model. Here is a link, but when you open it, search for hasManyThrough keyword and you will see an example.
P.S: With right keys naming you could achieve it with:
public function games()
{
return $this->hasManyThrough('App\Models\Games', 'App\Models\Teams');
}
EDIT #01
Since you have 2 types of teams, you can create 2 different relationships where each relationship will get you one of the type you need. Like this:
public function gamesAsHome()
{
return $this
->hasManyThrough('App\Models\Games', 'App\Models\Teams', 'club_id', 'home_id');
}
public function gamesAsGuests()
{
return $this
->hasManyThrough('App\Models\Games', 'App\Models\Teams', 'club_id', 'guest_id');
}
EDIT #02
Merging Relationships: To merge these 2 relationships, you can use merge() method on the Collection instance, what it will do is, it will append all the records from second collection into the first one.
$gamesHome = $model->gamesAsHome;
$gamesGuests = $model->gamesAsGuests;
$games = $gamesHome->merge($gamesGuests);
return $games->unique()->all();
Thanks to #HCK for pointing out that you might have duplicates after the merge and that unique() is required to get the unique games after the merge.
EDIT #03
sortBy also offers a callable instead of a attribute name in cases where Collection contains numerical indexing. You can sort your Collection like this:
$merged->sortBy(function($game, $key) {
return $game->created_at;
});
When you define that Club hasMany games you are indicating that game has a foreign key called club_id pointing to Club. belongsTo is the same but in the other way. These need to be coherent with what you have on your database, that means that you need to have defined those keys as foreign keys on your tables.
Try this...
Club model
public function games()
{
return $this->hasMany('App\Models\Games');
}
Game model
public function homeTeam()
{
return $this->belongsTo('App\Models\Team','home_id');
}
public function guestTeam()
{
return $this->belongsTo('App\Models\Team','guest_id');
}
Your Query like
Club::where('id',$id)->has('games.guestTeam')->get();

Load relations for a query result separately

Let's say we have two models Book and Author. The Book model defines a relationship to get the author:
public function author()
{
return $this->belongsTo(Author::class, 'author_id','id');
}
If we want to get all of the books that were published in 2000, along with their authors, we can do this:
$books = Book::with('author')->where('publication_year', 2000)->get();
Now what if we want to return the unique Authors who published in 2000 separately from their Books? Let's say Bob published three books and Sally published two books. We'd have one collection that included the 5 books without their authors, and another collection that included Bob and Sally once each.
This is the closest I've gotten (this is a simplified example and may contain typos):
$books = Book::with('author')->where('publication_year', 2000)->get();
$authors = $books->pluck('author')->unique('id');
//this part is bad:
foreach ($books as $book) {
unset($book['author']);
}
Is there a more efficient way to do this, or do I have to set up a manual JOIN?
If you want to get IDs of authors who have written books in 2000, you could use whereHas():
Author::whereHas('books', function($q) {
$q->where('publication_year', 2000);
})->pluck('id');
You can use whereIn as:
$books = Book::where('publication_year', 2000)->get();
$authors_id = $books->unique('author_id')->pluck('author_id');
$authors = Author::whereIn('id', $authors_id)->get();

Laravel Many To Many Polymorphic Relations

I have a quick question on how to define the below relationship
I have a USER that can belong to Many Councils, Many Schools and Many Businesses.
Now I know I can have a pivot table for all the above something like
council_user
school_user
business_user
This means I can pull out of the DB all councils a user belongs to, all businesses etc
To save me doing this is there a better approach that having 3 pivot tables, could I use Many To Many Polymorphic Relations and do it that way, if so does any one know how that would look as a table structure?
I feel it would be something like...
business
id - integer
name - string
council
id - integer
name - string
school
id - integer
name - string
userables
user_id - integer
userable_id - integer
userable_type - string
If so do I remove the userable_is and userable_type from my USER table and have this extra userables table?
Does any one know how this works, have I totally misunderstood what Polymorphic Relations does?
In Many to Many polymorphic relationship, you should have userables table along with users table. and userable_id and userable_type should be in userables table not in users table.
userables table:
user_id userable_id userable_type
1 1 App\Business
1 2 App\School
...
Business, School and Council Model:
public function users()
{
return $this->morphToMany('App\User', 'userable');
}
User Model:
public function businesses()
{
return $this->morphedByMany('App\User', 'userable');
}
public function schools()
{
return $this->morphedByMany('App\User', 'userable');
}
public function councils()
{
return $this->morphedByMany('App\User', 'userable');
}
then, this gives all the businesses of user with user_id 1.
$users=User::find(1);
foreach($user->businesses as $business)
{
print_r($business->name);
}
See Many to many polymorphic relation in doc.

How to retrieve data from many to many relationship tables with Doctrine

Let say I have 3 tables: Book, Author and BookAuthor.
Book has id, name
Author has id, name
BookAuthor has id, book_id, author_id
I want to find all books of an author, and I know author_id. Can anyone show me how to do this with Doctrine Query Language?
I think there are few ways:
1
In action:
$this->books= Doctrine::getTable('BookAuthor')->getBookAuthor($author_id);
In model:
public function getBookAuthor($author_id)
{
$q = $this->createQuery('a')
->Where('a.author_id=?',$author_id)
->addORDERBY ('created_at DESC');
return $q->execute();
}
2
Sometime for example you have some parameter in book table, for example book can be active or not.
In action:
$this->books= Doctrine::getTable('Book')->getBookAuthor($author_id);
In model:
public function getBookAuthor($author_id)
{
$q = $this->createQuery('a')
->andWhere('a.active=1')
->leftJoin('a.BookAuthor o')
->andWhere('o.author_id=?',$author_id)
->addORDERBY ('created_at DESC');
return $q->execute();
}

Resources