Laravel: efficient way to count related models with where clause - php

I have two models User and Posts
User: id,name
Post: id,title,post,user_id
I want to check if some user has posts with given title.
$user = User::find($userId);
$posts = $user->posts;
$postsWithGivenTitle = $posts->where('title','=',$title);
$postCount = $postsWithGivenTitle->count();
I suppose this above should work but I need to go above this and do it efficiently. So I got this and it's working but still not sure is it the right way to do it.
$user = User::find($userId)->withCount(['posts' => function ($q) use ($title) {
$q->where('title','=',$title);
}])->first();
and then to check the count:
if ($user->posts_count > 0) {
//do something
}
What's confusing me, and looks ugly, is using the methods find() and first() in the same query. So hopefully I'm missing something simple here and overthinking it.
Thanks

As you can see in the docs: https://laravel.com/docs/5.3/eloquent-relationships
You can acheive what you want like this:
$user = User::withCount(['posts' => function($q){....}])->find($id)

Related

Access to multilevel relationships in laravel

can someone help me on getting data from multi level relationships in laravel?
I want to do something like
BagageAnnouncement->Announcement->user->profile->"name of the field"
$data = $request->text;
$filter = BagageAnnouncement::whereHas('announcement',function ($query) {
})->whereHas('user', function ($query) {
})->whereHas('profile',function($query){
$query->where('level',$data)
})->get();
Thank you guys for you answers I finally figure out a solution
$data = $request->text;
$filter = BagageAnnouncement::whereHas(
'announcement.user.profile',
function ($q2) use ($data) {
$q2->where('level',$data);
}
)->get();
Not sure what you want as the result, but if you want a single result, you can do like this:
$bagage = BagageAnnouncement::where(specify_your_condition)->first();
$filter = $bagage->user->profile->name_of_the_field;
If you want an array of results, change the first() to get() and make use of foreach
Of course, you will need to set up the relationships in your models first.

Laravel: find if a pivot table record exists

I have two models which are joined by a pivot table, User and Task.
I have a user_id and a task_id.
What is the neatest way to check whether a record exists for this combination of user and task?
You have a couple options depending on your situation.
If you already have a User instance and you want to see if it has a task with a certain id, you can do:
$user = User::find(1);
$hasTask = $user->tasks()->where('id', $taskId)->exists();
You can reverse this if you have the Task instance and want to check for a user:
$task = Task::find(1);
$hasUser = $task->users()->where('id', $userId)->exists();
If you just have the ids, without an instance of each, you could do the following:
$hasPivot = User::where('id', $userId)->whereHas('tasks', function ($q) use ($taskId) {
$q->where('id', $taskId);
})
->exists();
You can also try this, this is working in Laravel 5.7
$user = User::find(1);
$hasTask = $user->tasks->contains($taskId);
But the better way will be
$hasTask = $user->tasks()->where('tasks.id', $taskId)->exists();
may be you search this?
$users = User::has('task')->get();
Easiest to read for me is:
$user->tasks()->exists();
You can use the code below:
$user = User::find(1);
$hasTask = $user->tasks()->where('task_id', $taskId)->exists();

Laravel modifying multiple rows at once

I have an array of $ids.
I'd like to essentially say:
foreach($ids as $id):
$user = User::find(1);
$user->life_expectancy -= 1;
$user->save();
endforeach;
Except I have thousands of ids in the array, and I'd much rather do something like:
$users = User::whereIn('id', $ids)->update(array('life_expectancy' => --1));
To just get it done in a single query. But that isn't going to work... is there another method?
I know I can update multiple users to all have the same life_expectancy, but I'd like it to be a modification of the previous value.
Check out this site, http://community.sitepoint.com/t/one-sql-statement-to-subtract-and-update-a-field-value/4673 if you decide to use a raw query, but looking on laravel's docs I think you can just do this,
$users = DB::table('users')
->whereIn('id', $ids)->decrement('life_expectancy');
App\User::whereIn('id',[1,2])->decrement('life_expectancy');
If you need -2 use next string:
App\User::whereIn('id',[1,2])->decrement('life_expectancy',2);

How to Merge Two Eloquent Collections?

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.

Performing a sum() on a collection in Laravel

I have an application with a basic forum system where users can "like" a topic multiple times. My models extend Eloquent and I'm trying to get the sum of votes a user has for a specific topic... Basically, I'm trying to accomplish something like:
$votes = Auth::user()
->votes->has('topic_id', '=', $topic->id)
->sum('votes');
However, when executing this, I get the following error...
Call to a member function sum() on a non-object
I've also tried
public function show($forumSlug, $topicSlug)
{
$topic = Topic::whereSlug($topicSlug)->first();
$votes = Topic::whereHas('votes', function ($q) use ($topic)
{
$q->where('topic_id', '=', $topic->id)->sum('votes');
});
dd($votes);
}
However, with that I receive an error stating:
Unknown column 'ideas.id' in 'where clause' (SQL: select sum(votes)
as aggregate from votes where votes.idea_id = ideas.id and
idea_id = 1)`
You may try something like this (Not sure about your relationship but give it a try):
$topic = User::with(array('topics' => function ($query) use ($topic_id) {
// $query = Topic, so it's: Topic::with('votes')
$query->with('votes')->where('topics.id', $topic_id);
}))->find(Auth::user()->id)->topics->first();
// Count of total votes
dd($topic->votes->count());
P/S: If it doesn't work then please post your model's relationship methods.
I managed to get it working, though I'm not sure I like this approach. I'd love to hear if anyone knows of a better way of doing this...
Basically, I used my relationships to filter() the votes and then used sum() on the filtered collection.
public function show($forumSlug, $topicSlug)
{
$userId = is_null(Auth::user()) ? false : Auth::user()->id;
$topic = Topic::whereSlug($topicSlug)->first();
$votes = $topic->votes->filter(function ($votes) use ($userId)
{
return $votes->user_id == $userId;
})->sum('votes');
return View::make('forums.topics.show', compact('topic', 'votes'));
}

Categories