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();
Related
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.
Based on the official Laravel documentation I found that :
$books = App\Book::with('author.contacts')->get();
In this case, imagine we have a structure like this :
books
books_authors
authors
contacts
books_authors_contacts
That's just an hypotetic case but I would like some more information : what about if I would like to retrieve all authors contacts for this specific book, imagining that for a specific book multiples authors could have multiple contacts (such contacts are linked through both authors and books as the entity books_authors_contacts says)
Is it possible to retrieve it through Laravel with eager loading with a belongsToMany relation?
By using the example below, it would just retrieve all of the contacts of an author no matter the book is.
Thanks you in advance!
There is tricks to gain this functionality in a very complex fashion, see this post. In a more practical sense, when these cases arrive in a professional environment, you often end up doing a method that aggregates this data. This can be done in multiple ways, this is an approach i feel is fairly easy and readable.
public class Book
{
public function contacts()
{
$contacts = new Collection();
$this->authors->each(function(Author $author) use($contacts) {
$contacts->concat($author->contacts);
})
return $contacts;
}
}
Offcourse for the eager loading, you have to remember to include the whole structure. For more Laravel approach contacts can be made as a Eloquent Mutator.
$book = App\Book::with('authors.contacts')->find(1);
$contacts = $book->authors();
I have three tables: users, accounts and hotels. Users and Accounts are connected with belongstoMany relation and Accounts and Hotels are connected the same way. Each User has Accounts and those Accounts have Hotels.
When I have Auth::user(), how I can return all hotels?
$accounts = Auth::user()->accounts()->get();
With the above statement, I can get all Accounts. How can I return all Hotels?
WHAT I TRIED?
public function index(Request $request)
{
$accounts = Auth::user()->accounts()->get();
$hotels = collect();
foreach ($accounts as $key => $a) {
$h = $a->hotels();
$hotels = $hotels->toBase()->merge($h);
}
dd($hotels);
return view('hotels.index',compact('hotels'));
}
but this code dont return me hotels collection which I can use in blade view files
Case 1
In the case you have a relationship as shown in the diagram below
What you are looking for is the hasManyThrough relationship.
From the Laravel documentation
The "has-many-through" relationship provides a convenient shortcut for accessing distant relations via an intermediate relation
In your case, on your User model, you can define the relationship
public function hotels()
{
return $this->hasManyThrough('App\Hotel', 'App\Account');
}
To then get your collection of hotels you can simply use
$hotels = Auth::user()->hotels;
You can also provide extra arguments to the hasManyThrough function to define the keys that are used on each table, great examples of this are given in the documentation linked above!
Case 2
If, instead, you have a relation as shown in the following diagram
It is a little more tricky (or, at least, less clean). The best solution I can think of, that uses the fewest queries is to use with.
$accounts = Auth::user()->accounts()->with('hotels')->get();
Will give you a collection of accounts, each with a hotels child. Now, all we have to do is get the hotels as a standalone collection, this is simple with some neat collection functions provided by Laravel.
$hotels = $accounts->flatMap(function($account) {
return $account->hotels;
})->unique(function ($hotel) {
return $hotel->id;
});
This will do the job of creating a collection of hotels. In my opinion, it would be cleaner and more efficient to simply make a new relationship as shown below.
And then to perform queries, using basic Eloquent methods.
How do you return a collection of a grouped dataset in a ManytoMany relationship with this scenario?
Here is a sample of what dataset I want to return
So let's take the favorites as the genres and the highlighted date is the genres name, it's also a collection as well. I want to group it based on the genres name in that collection.
My model:
Video
```
public function genres() {
return $this->belongsToMany(Genre::class);
}
```
Genre
```
public function videos() {
return $this->belongsToMany(Video::class);
}
```
I tried the following already but can't seem to get it.
```
$videos = Video::with('genres')->all();
$collection = $videos->groupBy('genres.name');
```
I want to group the dataset by the genres name knowing the genre's relationship is also a collection of genres.
Try something like:
Video::with('genres')->get()->groupBy('genres.*.name');
Or:
$videos = Video::with('genres')->all();
$collection = $videos->groupBy('genres.*.name');
Note that above is code you posted, just after replacing "genres.name" with "genres.*.name".
Just noticed the post is old, this at least works on latest Laravel.
Collections and query builders share many similar functions such as where() groupBy() and so on. It's nice syntax sugar, but it really does obscure the underlying tech.
If you call $model->videos... like a property, that's a collection (query has executed).
If you call $model->videos()... like a method, that's a query builder.
So if you want to get the job done in sql, you can do something like...
$video_query_builder = Video::with('genere');
$video_query_builder->groupBy('genere_id');
$result = $video_query_builder->get();
You can chain it all together nice and neatly as was suggested in the comments... like this:
$result = Video::with('genere')
->groupBy('genere_id')
->get();
I have two Eloquent models (say: Books and Authors) which have a one-to-many-relation (one Author can have many Books, each Book has exactly one Author). The database also contains Authors which have no Book at all. I would like to retrieve a list of all Authors which have a Book - whitout having duplicate authors.
Currently I loop over all Book instances and add the Author to the list if it is not yet in the list:
$books = Books::all();
$list = [];
foreach ($books as $book) {
if (! in_array($book->author, $list) ) {
array_push($list, $book->author);
}
}
I wonder whether there is a smarter way to get this, i.e. a way, which reuduces the number of database queries.
I would like to retrieve a list of all Authors which have a Book - whitout having duplicate authors
Use the has() method:
$authorsWithBooks = Author::has('books')->get();
$books = Books::with('author')->get();