Laravel Query Relationship on One Model Instance - laravel-5

I am aware that I can use count() to query for Eloquent relationships in Laravel, like so:
if(count($question->answers()))
Where answers() is a hasMany relationship:
public function answers()
{
return $this->hasMany('App\Models\Answer', 'question_id');
}
My question is, how do I do this when $question is not an entire collection but one Model instance?
$question = Question::where('id',$key)->first();
How do I query the above question, and only that question, for a potential relationship using count()?
I always am getting a count() of greater than zero, even when the selected question has no associated answers, which means my if block always runs and returns unwarranted null values:
if(count($question->answers()))
{
//returns nulls
}

Since calling $question->answers() is returning a QueryBuilder instance, calling count() on that will most likely always return 1. If you access $question->answers (as a property and not a method), or use the full logic $question->answers()->get(); it should properly return a Collection, which count() will function correctly on:
$question = Question::where('id',$key)->first();
if(count($question->answers) > 0){
// Do something
}
// OR
if(count($question->answers()->get()) > 0){
...
}
As suggested by #maraboc, you could also eager load your $question with answers using a ->with() clause:
$question = Question::with(["answers"])->where('id',$key)->first();
But even in this case, $question->answers() would still be returning a QueryBuilder instance, so access it as a property for count() to function correctly.

As already pointed count($question->answers()) has no meaning because $question->answers() is a Relation instance, you can call dynamic query method on that but if you want to count elements you need a collection, i.e $question->answers.
So you have two choice:
count the collection: count($question->answers)
ask the database to do the count: $question->answers()->count()
Parentheses matters

Related

Call to a member function first() on null

When I try to fetch my user data, I receive the error
Call to a member function first() on null
public function show($id) {
$user=User::findOrFail($id);
$employee = $user->employees->first();
return view('admin.profile')
->with(['employee' => $employee , 'user' => $user]);
}
The problem is probably in your User model.
Check that you have declared the employees relationship:
public function employees()
{
return $this->hasMany(Employee::class); // I'm assuming you have a Employee model with expected column names, but feel free to replace everything with what you actually have in your app
}
If the problem persists, edit your question with your tables structure and your models.
It's very useful to understand the difference between $user->employees and $user->employees().
$user->employees: returns a Collection of employee models, or null if none are found.
$user->employees(): returns a query builder instance that you can chain additional conditions to (where's, etc).
Both options have a first() option available to them, but one is using a Collection method, where the other is using the query builder method.
Some have already suggested this, and I will as well - the safer and simplest solution to your problem is to use the query builder version of the relationship, since there is no risk of the employees() result being null. It also has the added benefit of not needing to load the entire relationship into a collection just to get the first result.
In short: $user->employees()->first(); is the best way to go.

What is difference between $this->Products and $this->Products() in laravel model?

I got different result from getReward1 and getReward2:
Model:
class User extends Authenticatable
{
public function Products()
{
return $this->hasMany('App\Product', 'user_id');
}
public function getReward1()
{
return $this
->Products
->where('reward', '>', 0)
->where('status', 0)
->sum('reward'); // sum = 7,690,000
}
public function getReward2()
{
return $this
->Products()
->where('reward', '>', 0)
->where('status', 0)
->sum('reward'); // sum = 7,470,000
}
}
getReward1 return 7,690,000 and getReward2 return 7,470,000 (Two different values)
What is difference between $this->Products and $this->Products() ?
$this->products;
// Returns a Collection
$this->products();
// Returns a Relation instance, which is a query builder and can be of type HasMany, BelongsTo...
$this->products()->get();
// Is EXACTLY like doing $this->products for the first time.
The main difference is that products() is just a query that hasn't been executed yet, whereas products are the actual results of this query.
Honestly, even if the name is the same and can be confusing, there are no other similarities between them.
A simple analogy:
DB::table('products')->where('user_id', 18); //could be the $user->products()
DB::table('products')->where('user_id', 18)->get(); //could be $user->products
It's just an analogy, it's not exactly like this internally, but you get the point.
To add more confusion on top of it, Collection methods are ofter similar to those you find in queries; both have where(), first()...
The main thing to remember is that with parentheses, you are still building a query. Until you call get or first, you remain in a query builder.
Without, you already have your results, you are in a Collection (https://laravel.com/docs/8.x/collections).
About the difference you get between getReward1 and getReward2, it's hard to tell exactly what's happening without seeing your database structure.
It can be a lot of things, but when you are calling the sum method, you are calling it on a Collection instance in getReward1 and on a query builder in getReward2 (you are actually executing a query with SELECT SUM(reward)...).
$this->Products() will return an instance of the query builder. The subsequence where clauses will constrain the DB query and then return only the product that you want. These will not be stored in the model instance.
$this->Products will get all of the products from the DB and store them in the model instance as an Eloquent Collection. The subsequent where clauses will then be performed on the Eloquent Collection.
Essentially, the method is doing everything in the DB, whereas, the property is fetching all of the rows and then limiting it with PHP.

Why does groupBy() work but Count() does not in laravel eloquent model function?

I need to get counts of all the records based on belongsToMany relationship. normally I can use groupBy() in a function inside the model. but if I use count() or withCount() inside a model function, i get the error as followed:
function code:
public function TaskCount(){
return $this->belongsToMany(User::class)->count();
}
Error message:
Symfony\Component\Debug\Exception\FatalThrowableError: Call to a member function addEagerConstraints() on int in file /Users/dragonar/Dev/iyw/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php on line 560
If I do the following...
public function TaskCount(){
return $this->belongsToMany(User::class)->Count();
}
//expected record is 4(int)
//output is 4(array) user records.
...it gives me data but like 4 records of the user instead of a number 4. The user data is useless. The only thing needed is totalCount for those records.
Relationship methods have to return Relation type objects. You are returning the result of a query, count() returns a number not the Relation object / Builder. Remove the count from that statement you are returning. Renamed the relationship tasks here.
public function tasks()
{
return $this->belongsToMany(User::class);
// this returns a Relation type object a BelongsToMany object
}
Where you need to use that relationship you can then use count:
$something->tasks()->count();
Or you can load the count of the relationship using loadCount:
$something->loadCount('tasks');
$something->tasks_count;
Or via eager loading for a collection:
$results = Something::withCount('tasks')->get();
foreach ($results as $model) {
echo $model->tasks_count;
}
If you really wanted to you could create an accessor to get the count as well, you just may want to avoid the N+1 issue by preloading the relationship and using the dynamic property to access it in the accessor.
These relation objects are Builders. When you called groupBy on it previously that is returning the Builder, it isn't executing the query. You can add where conditions and order by statements because they are just building the query, not executing it, they return the builder you are calling the method on.
Laravel 6.x Docs - Eloquent - Relationships - Counting Related Models withCount loadCount
Why not use: Task::all()->count(); ?
you can use the withCount method while calling relation like this
User::withCount('images')->get();
You can add get the data and just count it.
public function TaskCount(){
return $this->belongsToMany(User::class)->get()->count();
}
You can call it like
$taskCount = $task->TaskCount();

Laravel using find() and get() together

I have a table 'tour2s' with 2 rows and when I do:
$tour = Tour2::find(1);
dd($tour);
it returns the tour with 'id' = 1. And it's Object.
I want to turn the object to collection of only attributes of the model, nothing else. And I know that when I use ->get() it returns collection.
But when I am trying:
$tour = Tour2::find(1)->get();
dd($tour);
It returns a collection but of all 2 tour objects (full objects, not only attributes):
I did it like:
$tour = Tour2::find(1);
$tour = collect($tour);
dd($tour);
and now it's what i what - it return a collection of only model attributes (WHAT I WANTED):
SO, my question is why when I used $tour=Tour2::find(1)->get() it returned all tours not only the one with 'id'=1 ?
Passing an array to find() will return a collection.
$tour = Tour2::find([1]);
However, it will be a collection of Tour2 objects, not only the attributes.
Then, if you want only the attributes, you could use $tour->toArray()
You could also do $tour = collect(Tour2::find(1));
And to answer your question, when you use $tour=Tour2::find(1)->get(), Laravel fetch the first tour, and then calling get() on $tour will fetch all other records, so return two tours in your case.
Ok, the main question, as i understand is: "Why when i wrote Tour2::find(1)->get() i receives collection of all records".
when you wrote Tour2::find(1) it assumes that you receive instanse of model Tour2. So we can simple write $tourInstanse->get()
If you go to \Illuminate\Database\Eloquent\Model you can see that here is no method called get() but we have a magic method __call. Look at his implementation:
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
return $this->newQuery()->$method(...$parameters);
}
So, when you call get() method on a model instance you get model`s QueryBuilder (as described in last row) and call get() method on a QueryBuilder. As a result, you receiving all records of that model Class.

Laravel sortBy not having any affect

I have a custom attribute on my User model that's calculates the length of some other tables and returns an integer value:
public function GetCurrentQueueLengthAttribute()
{
// return int
}
I then have an API endpoint that returns a "Team" with all its users (simple Spark pivot)
public function show($teamId)
{
$query = Team::query();
$query->with('users')->where('id', $teamId);
$team = $query->first();
return $team->users->sortBy('currentQueueLength');
return $team;
}
The issue is that the returned data doesn't change order. There are no errors, just the same order of the users every time.
Is there something I'm missing?
The sortBy function is not to be mistaken by the orderBy function, the first one sorts a collection, the second one alters the sql of the query builder.
To be able to use the sortBy function one first needs to retrieve the collection. These functions can still be chained by using:
return $team->users()->sortBy('currentQueueLength');
optionally one could also use orderByRaw if you are willing to write a custom sql query for the sorting.

Resources