How to Merge Two Eloquent Collections? - php

I have a questions table and a tags table. I want to fetch all questions from tags of a given question. So, for example, I may have the tags "Travel," "Trains" and "Culture" attached to a given question. I want to be able to fetch all questions for those three tags. The tricky, so it seems, is that questions and tags relationship is a many-to-many defined in Eloquent as belongsToMany.
I thought about trying to merge the questions Collections as below:
foreach ($question->tags as $tag) {
if (!isset($related)) {
$related = $tag->questions;
} else {
$related->merge($tag->questions);
}
}
It doesn't seem to work though. Doesn't seem to merge anything. Am I attempting this correctly? Also, is there perhaps a better way to fetch a row of rows in a many-to-many relationship in Eloquent?

The merge method returns the merged collection, it doesn't mutate the original collection, thus you need to do the following
$original = new Collection(['foo']);
$latest = new Collection(['bar']);
$merged = $original->merge($latest); // Contains foo and bar.
Applying the example to your code
$related = new Collection();
foreach ($question->tags as $tag)
{
$related = $related->merge($tag->questions);
}

The merge() method on the Collection does not modify the collection on which it was called. It returns a new collection with the new data merged in. You would need:
$related = $related->merge($tag->questions);
However, I think you're tackling the problem from the wrong angle.
Since you're looking for questions that meet a certain criteria, it would probably be easier to query in that manner. The has() and whereHas() methods are used to generate a query based on the existence of a related record.
If you were just looking for questions that have any tag, you would use the has() method. Since you're looking for questions with a specific tag, you would use the whereHas() to add the condition.
So, if you want all the questions that have at least one tag with either 'Travel', 'Trains', or 'Culture', your query would look like:
$questions = Question::whereHas('tags', function($q) {
$q->whereIn('name', ['Travel', 'Trains', 'Culture']);
})->get();
If you wanted all questions that had all three of those tags, your query would look like:
$questions = Question::whereHas('tags', function($q) {
$q->where('name', 'Travel');
})->whereHas('tags', function($q) {
$q->where('name', 'Trains');
})->whereHas('tags', function($q) {
$q->where('name', 'Culture');
})->get();

$users = User::all();
$associates = Associate::all();
$userAndAssociate = $users->merge($associates);

Merge two different eloquent collections into one and some objects happen to have the same id, one will overwrite the other. Use push() method instead or rethink your approach to the problem to avoid that.
Refer to web

Creating a new base collection for each eloquent collection the merge works for me.
$foo = collect(Foo::all());
$bar = collect(Bar::all());
$merged = $foo->merge($bar);
In this case don't have conflits by its primary keys.

I have faced some issue by using merge. So I used concat. You can used it like below.
$users = User::all();
$associates = Associate::all();
$userAndAssociate = $users->concat($associates);

All do not work for me on eloquent collections, laravel eloquent collections use the key from the items I think which causes merging issues, you need to get the first collection back as an array, put that into a fresh collection and then push the others into the new collection;
public function getFixturesAttribute()
{
$fixtures = collect( $this->homeFixtures->all() );
$this->awayFixtures->each( function( $fixture ) use ( $fixtures ) {
$fixtures->push( $fixture );
});
return $fixtures;
}

I'm sorry about that, but since PHP 7.4 you're available to do like this (better use merge).
$foo = Foo::all();
$bar = Bar::all();
/** $foo will contain $foo + $bar */
$foo->push(...$bar);

I would like to add that, i found that the concat method does not seem to override based on ID, while the merge method does. concat seems to work for me, while merge caused issues.

Related

Get comment sub-comments when only one sub-comment

I have comments table where has parent_id
This is Comment table sub_comments relation.
public function sub_comments()
{
return $this->hasMany(self::class, 'parent_id');
}
This code return all comments with related all sub-comments
Comment::with('sub_comments')->get();
But I want to get all comments also sub-comments when sub-comments is single. That mean if comment have 2 or more comments for that comment I did not want get that sub-comments.
Now I use this code
$oneSubcommentCommentIds = Comment::has('sub_comments', '=', 1)->pluck('id');
Comment::with([
'sub_comments' => function ($q) use ($oneSubcommentCommentIds) {
$q->whereIn('parent_id', $oneSubcommentCommentIds);
}
])->get();
but this make one additional query.
Try this:
Comment::with('sub_comments')->has('sub_comments', '=', 1)->get();
Update
Your question wasn't clear, I can't imagine another way to doing this without previosly loaded the relationship or the count of the relationship.. so I'd do this:
// First get all your comments with an aditional count field
$comments = Comments::withCount('sub_comments')->get();
// separate the ones with just one sub_comment from the rest
list($oneSubComment, $theRest) = $collection->partition(function ($comment) {
return $comment->sub_comments_count == 1;
});
// Then load the relationship on just the selected elements
$oneSubComment->load('sub_comments');
// re-join the collection
$comments = $oneSubComment->union($theRest);
What am I doing here?
Adding an additional field to each $comment with the relationship count (it should be something like sub_comments_count)
Partition the resulting collection in two parts: the ones with one comment and the rest. Using the partition() method.
Lazy eager loading the collection.
Re-joining the two collections using the union() method.

Is there a way to (re)map Laravel Collections with an index from its content?

I have some Newsletters:
$newsletters = $channel->Newsletter()->whereIn('id', $wantNewsletters)->get();
Which result in this collection:
I tried to use pluck, but it just allows one column:
$newsletters = $channel->Newsletter()->whereIn('id', $wantNewsletters)->pluck('media', 'id');
What I try to achieve is this (pardon my sad paintshop skillz :D)
In a way, that's like ->pluck('*', 'id'). For now I had to foreach every collection and this does not seem right to me.
Thanks for your help!
You can use keyBy method:
$newsletters = $channel->Newsletter()->whereIn('id', $wantNewsletters)->get()->keyBy('id');

How to get different queries (models) results with one statement? - laravel

Sorry if my title is confusing, not sure how to explain this within a line. Let's say I have a table with some columns and I have this
$model = Document::where('systemName', '=', $systemName)->where('ticketNumber', '=', ($nextTicketNumber))->get(); ticketNumber is unique where as there are quite a few systemNames
The above will get exactly what I want but I want more. I want an another array which will store all the rows under the same systemName. I know I can do this by doing
$allSystemNameModel = Document::where('systemName', '=', $systemName)
But is there a possible way to not having two variables and be easier?
No, you can't get both collections into one variable with one statement, however, you can create an array and store your results there:
$both = [];
$both['model'] = ...
$both['all'] = ...
UPDATE:
To avoid querying the database twice, you can use a first method that laravel provides us with.
$allSystemNameModel = Document::where('systemName', '=', $systemName);
$model = $allSystemNameModel->first(function ($doc) use ($nextTicketNumber) {
return $doc->ticketNumber == $nextTicketNumber;
});
$both['model'] = $model;
$both['all'] = $allSystemNameModel->all();
Note: Be sure to use use when working with php closures since $nextTicketNumber will be undefined otherwise.

How to count and fetch data query in laravel?

How to merge this two queries ?
$data = DB::table('category_to_news')
->where('category_to_news.name', ucwords($category))
->remember(1440)
->count();
and
$data = DB::table('category_to_news')
->where('category_to_news.name', ucwords($category))
->remember(1440)
->get();
So, as far as I understand from your comment, you simply want to get all records from the table category_to_news and you want to know how many records are in there, right?
MySQL's count is an aggregate functions, which means: It takes a set of values, performs a calculation and returns a single value. If you put it into your names-query, you get the same value in each record. I'm not sure if that has anything to do with 'optimization'.
As already said, you simply run your query as usual:
$data = DB::table('category_to_news')
->where('name', ucwords($category))
->remember(1440)
->get(['title']);
$data is now of type Illuminate\Support\Collection which provides handy functions for collections, and one them is count() (not to be confused with the above mentioned aggregate function - you're back in PHP again, not MySQL).
So $data->count() gives you the number of items in the collection (which pretty much is an array on steroids) without even hitting the database.
Hi DB class dont return collection object it give error "call member function on array" but eloquent return collection object. for above code we can use collect helper function to make it collection instance then use count and other collection methods https://laravel.com/docs/5.1/collections#available-methods .
$data = DB::table('category_to_news')
->where('name', ucwords($category))
->remember(1440)
->get();
$data = collect($data);
$data->count();
You my get it using:
$data = DB::table('category_to_news')
->where('name', ucwords($category))
->remember(1440)
->get();
To get the count, try this:
$data->count();
Why you are using DB::table(...), instead you may use Eloquent model like this, create the model in your models directory:
class CategoryToNews extends Eloquent {
protected $table = 'category_to_news';
protected $primaryKey = 'id'; // if different than id then change it here
}
Now, you may easily use:
$data = CategoryToNews::whereName(ucwords($category))->get();
To get the count, use:
$data->count();

Eloquent and Pivot Tables in Laravel 4

I have a Poll table, a Students table, and a pivot table between them that includes a token and their three votes.
public function students()
{
return $this->belongsToMany('Student', 'polls_students')->withPivot('token','first','second','third');
}
While working out saving the poll results, I came across some odd behavior that I don't quite understand. I'm hoping somebody can explain what it is I'm missing:
$poll = Poll::find(Input::get('poll_id'));
foreach($poll->students()->where('students.id', '=', Input::get('student_id'))->get() as $student){
var_dump($student->pivot->token);
}
$student = $poll->students()->where('students.id', '=', Input::get('student_id'))->get();
var_dump($student->pivot->token);
In the above code, the foreach loop will successfully display the token, where the second one throws the exception Undefined property: Illuminate\Database\Eloquent\Collection::$pivot
What am I missing? Are these two calls not logically creating the same object? How is 'pivot' working on the first and not the latter?
You first example:
$poll = Poll::find(Input::get('poll_id'));
foreach($poll->students()->where('students.id', '=', Input::get('student_id'))->get() as $student){
var_dump($student->pivot->token);
}
Here $poll->students() retrieves a collection and because of foreach loop you get a single object in your $student variable and you can use $student->pivot->token
You second example:
$student = $poll->students()->where('students.id', '=', Input::get('student_id'))->get();
var_dump($student->pivot->token);
Here you are doing same thing, using $poll->students() you are getting a collection but this time you are not using a loop and trying to do same thing using $student->pivot->token but it's not working because you didn't define any index from which you want to get the pivot->token, if you try something like this
$student->first()->pivot->token
Or maybe
$student->get(1)->pivot->token
Or maybe you can use first() instead of get() like this
$student = $poll->students()->where('students.id', '=', Input::get('student_id'))->first();
Then you can use
$student->pivot->token
Remember that, get() returns a collection even if there is only one record/model.
$poll = Poll::find(Input::get('poll_id'));
foreach($poll->students as $student){
var_dump($student->pivot->where('student_id',$student->id)->where('poll_id',$poll->id)->first()->token);
}

Categories