Eloquent nesting with clause in the middle relation - laravel

I have the following relationships:
User [many-to-many with] Assignments
Assignments [many-to-many with] Badges
The assignment_user pivot table has a flag 'is_finished' which I need to be true
These are my relationships in the matching models:
User.php
public function assignments()
{
return $this->belongsToMany('Assignment')->withPivot('is_finished')->withTimestamps();
}
Assignment.php
public function badges()
{
return $this->belongsToMany('Badge', 'badge_assignment')->withTimestamps();
}
Badge.php
public function assignments()
{
return $this->belongsToMany('Assignment', 'badge_assignment')->withTimestamps();
}
I'm trying to get all the Badges for my User's finished Assignments. This was my query:
$user = User::with(array(
'assignments' => function($query) {
$query->where('is_finished', '=', true)->orderBy('updated_at', 'desc');
})
)
->with('badges')
->username($name)->first();
However this is rendering the error "Call to undefined method Illuminate\Database\Query\Builder::badges()" when I use foreach($user->assignments->badges ...).
I then tried to get badges inside the scope of the first with, but I don't know how to reach is_finished:
$user = User::with(array(
'assignments.badges' => function($query) {
$query->where('is_finished', '=', true)->orderBy('updated_at', 'desc');
})
)
->username($name)->first();
With this I get "column not found", because the query is looking for is_finished within the badges, not the assignment_user relationship.
As I was having this issue I tried this just to be sure:
User::with('assignments.badges')->whereUsername($username);
... and I'm still getting "Undefined property: Illuminate\Database\Eloquent\Collection::$badges".
What am I doing wrong?

You need to load nested assignments.badges like this:
$user = User::with(array(
'assignments' => function($query) {
$query->where('is_finished', '=', true)->orderBy('updated_at', 'desc');
})
)
->with('assignments.badges')
->username($name) // do you have a scope for this or should it be whereUsername?
->first();

Related

retrieving related field in controller index function gives error but ok in show function

I define the relation in Company table (where I added the plural):
protected $table = 'companies';
public function country() {
return $this->belongsTo(Country::class, "country_id")->withDefault(['country' => 'unknown']);
}
I also did the same in the Country model.
When I use the following code in the controller show function it works:
public function show (Company $company) {
$company->country = $company->country()->pluck('country');
But if I use the same code in the index function in a loop, I get an error "Call to undefined method stdClass::country()":
public function index (Company $company) {
if (request('tag')) {
$companies = Tag::where('name',request('tag'))->firstOrFail()->companies;
$companies->page_title = "Businesses matching tag '".request('tag')."'";
} else {
$companies = DB::table('companies')
->where([['is_active', '=', '1']])
->orderBy('company')
->get();
}
foreach($companies as $key => $thisCompany) {
...
$thisCompany->country = $company->country()->pluck('country');
}
I guess it is due to the fact that $company is created in the loop and not passed through the function like in show(Company $company)... but I could not find how to solve this issue... so help will be appreciated.
I have added the model in the argument of the function and change the name of the $company variable in the loop by $thisCompany to avoid confusion with the $company model.
No error but the field $country->country does not contain the name of the country but "Illuminate\Support\Collection {#443 …1}"
Why is it so complicated? Please help...
Paul, sorry, I think I didn't explain myself well in the comments.
What I meant by "What about if you change DB::table('companies') by Company?", is to stop using DB Query Builder to use the Eloquent Company model.
Specifically in this segment of code:
$companies = DB::table('companies')
->where([['is_active', '=', '1']])
->orderBy('company')
->get();
So, it could be:
$companies = Company::where([['is_active', '=', '1']])
->orderBy('company')
->get();
The explanation is that in the first way (with DB Query Builder), the query will return a collection of generic objects (the PHP stdClass object) that do not know anything about the Company and its relationships.
On the other hand, if you use the Eloquent model Company, it will return a collection of Company objects, which do know about relationships, and specifically the relationship that you have defined as country.
Then, when you loop over the collection, you will be able to access the country relation of each Company object:
foreach($companies as $key => $company) {
//...
$company->country = $company->country()->pluck('country');
}
Finally, your code could looks like:
public function index () {
if (request('tag')) {
$companies = Tag::where('name',request('tag'))->firstOrFail()->companies;
$companies->page_title = "Businesses matching tag '".request('tag')."'";
} else {
$companies = Company::where([['is_active', '=', '1']])
->orderBy('company')
->get();
}
foreach($companies as $key => $company) {
//...
$company->country = $company->country()->pluck('country');
}
//...
}

Laravel - Get array with relationship

I have an ajax call that returns an array:
$reports = Report::where('submission_id', $submissionID)
->where('status', 'pending')
->get(['description','rule']);
return [
'message' => 'Success.',
'reports' => $reports,
];
From this array, I only want to return the fields 'description' and 'rule'. However I also want to return the owner() relationship from the Report model. How could I do this? Do I have to load the relationship and do some kind of array push, or is there a more elegant solution?
You can use with() to eager load related model
$reports = Report::with('owner')
->where('submission_id', $submissionID)
->where('status', 'pending')
->get(['id','description','rule']);
Note you need to include id in get() from report model to map (owner) related model
you will have probably one to many relationship with Reports and owners table like below
Report Model
public function owner() {
return $this->belongsTo('App\Owner');
}
Owner Model
public function reports() {
return $this->hasMany('App\Report');
}
your controller code
$reports = Report::with('owner')->
where('submission_id', $submissionID)->where('status', 'pending')->get()
return [
'message' => 'Success.',
'reports' => $reports,
];
This is what I ended up going with:
$reports = Report::
with(['owner' => function($q)
{
$q->select('username', 'id');
}])
->where('submission_id', $submissionID)
->where('status', 'pending')
->select('description', 'rule','created_by')
->get();
The other answers were right, I needed to load in the ID of the user. But I had to use a function for it to work.

How get data in method from others tables?

This is my simple db schema. Now in my group table i have 'group_A' and 'group_B'. In questions table i have 10 questions 5 for group_A and 5 for group_B. Users table is one user with ID = 1. What i try to do is get data like this:
SELECT answer
FROM answers
JOIN questions q ON (q.id = answer.question_id)
JOIN group g ON (g.id = q.group_id)
WHERE user_id = 1 AND g = 'group_A'
I have model in users class and i would like create method to get answers depend from group:
public function getAnswers($group) {
return $this->hasMany('App\Answers', 'question_id', 'id');
}
How can i get this in that method ? Should i create method group in questions class ?
Not sure about how to define such relation in model which takes input parameter but you could define a scope in your model to get the answers filtered by group like
class Answer extends Model {
public function user()
{
return $this->belongsTo('App\User');
}
public function question()
{
return $this->belongsTo('App\Question');
}
public function scopeGroup($query, $name)
{
return $query->whereHas('question.group', function($q) use($name){
$q->where('name', '=', $name);
});
}
}
I assume in answers model you have defined relationship for question and similarly in question model you have defined relationship for group model.
In query builder you could write it as
$answers = Answer::group('group_A')
->whereHas('user', function($q) use($userid){
$q->where('id', '=', $userid);
})->get();
Or you could apply filter on eager loaded relations as
$users = User::with(['answers' => function($query)
{
$query->whereHas('question.group', function($q){
$q->where('name', '=', 'group_A');
});
}])->get();
If you already have user object you can get answers for specific group as
$answers = $user->answers()
->whereHas('question.group', function($q){
$q->where('name', '=', 'group_A');
})->get();

Callback function of with() returns empty collection

I have three tables: realties, room_types and realty_room_type:
realty_room_type
----------------
id
realty_id
room_type_id
room_type
----------------
id
code
In my Realty model I set a rooms() relationship:
public function rooms()
{
return $this->hasMany(Room::class);
}
I am trying to eager load the rooms() relationship using the with() method. I want to custom what is returned from the relationship, so I am passing a callback function like this:
$realty = Realty::
where('id', $realtyId)
->with([
'rooms' => function ($query) use ($realtyId) {
$query
->leftJoin('room_types', 'room_types.id', '=', 'realty_room_type.room_type_id')
->selectRaw('code, COUNT(*)')
->groupBy('code');
}
])
->get()
);
The problem is I get an empty collection when accessing the relationship using $realty->rooms. Any idea why?
However if I dump and die the statements of the callback function like this:
Realty::
where('id', $realtyId)
->with([
'rooms' => function ($query) use ($realtyId) {
dd($query
->leftJoin('room_types', 'room_types.id', '=', 'realty_room_type.room_type_id')
->selectRaw('code, COUNT(*)')
->groupBy('code'));
}
])
->get()
);
I get what I'd like to be in the rooms() relationship.
Thank you in advance.
You don't need to return inside callback function and call get(). Here you can find the details.
$realty = Realty::
where('id', $realtyId)
->with([
'rooms' => function ($query) use ($realtyId) {
$query
->leftJoin('room_types', 'room_types.id', '=', 'realty_room_type.room_type_id')
->selectRaw('code, COUNT(*)')
->groupBy('code');
}
])
->get();

Where NOT in pivot table

In Laravel we can setup relationships like so:
class User {
public function items()
{
return $this->belongsToMany('Item');
}
}
Allowing us to to get all items in a pivot table for a user:
Auth::user()->items();
However what if I want to get the opposite of that. And get all items the user DOES NOT have yet. So NOT in the pivot table.
Is there a simple way to do this?
Looking at the source code of the class Illuminate\Database\Eloquent\Builder, we have two methods in Laravel that does this: whereDoesntHave (opposite of whereHas) and doesntHave (opposite of has)
// SELECT * FROM users WHERE ((SELECT count(*) FROM roles WHERE user.role_id = roles.id AND id = 1) < 1) AND ...
User::whereDoesntHave('Role', function ($query) use($id) {
$query->whereId($id);
})
->get();
this works correctly for me!
For simple "Where not exists relationship", use this:
User::doesntHave('Role')->get();
Sorry, do not understand English. I used the google translator.
For simplicity and symmetry you could create a new method in the User model:
// User model
public function availableItems()
{
$ids = \DB::table('item_user')->where('user_id', '=', $this->id)->lists('user_id');
return \Item::whereNotIn('id', $ids)->get();
}
To use call:
Auth::user()->availableItems();
It's not that simple but usually the most efficient way is to use a subquery.
$items = Item::whereNotIn('id', function ($query) use ($user_id)
{
$query->select('item_id')
->table('item_user')
->where('user_id', '=', $user_id);
})
->get();
If this was something I did often I would add it as a scope method to the Item model.
class Item extends Eloquent {
public function scopeWhereNotRelatedToUser($query, $user_id)
{
$query->whereNotIn('id', function ($query) use ($user_id)
{
$query->select('item_id')
->table('item_user')
->where('user_id', '=', $user_id);
});
}
}
Then use that later like this.
$items = Item::whereNotRelatedToUser($user_id)->get();
How about left join?
Assuming the tables are users, items and item_user find all items not associated with the user 123:
DB::table('items')->leftJoin(
'item_user', function ($join) {
$join->on('items.id', '=', 'item_user.item_id')
->where('item_user.user_id', '=', 123);
})
->whereNull('item_user.item_id')
->get();
this should work for you
$someuser = Auth::user();
$someusers_items = $someuser->related()->lists('item_id');
$all_items = Item::all()->lists('id');
$someuser_doesnt_have_items = array_diff($all_items, $someusers_items);
Ended up writing a scope for this like so:
public function scopeAvail($query)
{
return $query->join('item_user', 'items.id', '<>', 'item_user.item_id')->where('item_user.user_id', Auth::user()->id);
}
And then call:
Items::avail()->get();
Works for now, but a bit messy. Would like to see something with a keyword like not:
Auth::user()->itemsNot();
Basically Eloquent is running the above query anyway, except with a = instead of a <>.
Maybe you can use:
DB::table('users')
->whereExists(function($query)
{
$query->select(DB::raw(1))
->from('orders')
->whereRaw('orders.user_id = users.id');
})
->get();
Source: http://laravel.com/docs/4.2/queries#advanced-wheres
This code brings the items that have no relationship with the user.
$items = $this->item->whereDoesntHave('users')->get();

Resources