Deeply nested relationships filtering - php

Recently I faced problem accessing and filtering deeply nested relationship, so I decided to seek for a help.
So, I have this db structure:
http://s21.postimg.org/motrjy3dj/Screenshot_from_2015_07_24_12_14_51.png
And I need to get all teams within a project, and then for each team I need to get assigned users (of that team).
So far so good, my problem starts when I try to get the offer for each user. User can have only one offer for the assigned team which brings me to a problem.
Here is my code:
$project = Project::with("variants")
->with(array(
"teams" => function($query) {
$query->with(array(
"users" => function($query) {
$query->with("offers");
}
));
}
))
->find($projectID);
I have a hasManyThrough relationship "offers" in the "User" model which returns me all offers for user, but actually I just need (one) offer for related team_user table.
I tried filtering offers with scopes but it's a bad solution, because for each user I have additional query to db..
Is there some way to filter these offers dynamically?
Thanks!

I highly recommend using joins for this sort of complex query:
$projectOffers = Project
->join('team', 'team.project_id', '=', 'project.id')
->join('team_user', 'team_user.team_id', '=', 'team.id')
->join('user', 'user.id', '=', 'team_user.user_id')
->join('offer', 'offer.id', '=', 'team_user.offer_id')
->get([
'project.id',
'team.id AS team_id',
'user.id AS user_id',
'offer.id AS offer_id',
// any other columns you want
]);

Related

How to set alias table name in Laravel table model

I have this query builder in Laravel:
$obj_table = MyTable::get();
Now, how to make my MyTable to have alias name?
The reason I need this, I need to use with() and Joining with my another tables.
Anyone got the same problems with me?
Thanks!
From laravel documentaion
You may also alias the relationship count result, allowing multiple counts on the same relationship:
$posts = Post::withCount([
'comments',
'comments AS pending_comments' => function ($query) {
$query->where('approved', false);
}
])->get();
For more information have a look at
https://laravel.com/docs/5.4/eloquent-relationships
Cheers,
Hope this helps

Eloquent eager loaded query not quite working with joins

I'm working on a Chemical database and am learning Eloquent as I go. This is in Slim Framework, not Laraval itself.
This thread helped get me close to what I need, but now I'm seeing something odd and I haven't been able to find a solution, though lots of people asking similar questions.
I have
$chemicals = $app->Chemical->with(array('Company', 'Room', 'Location', 'Measurement'))
->join('company', 'company_id', '=', 'company.id')
//->join('room', 'room_id', '=', 'room.id')
//->join('location', 'location_id', '=', "location.id")
->where('company', '=', 'ROUSSEL')
->get();
Notice the 2 Joins commented out. Those 2 fields do display properly in my table, but company is blank. If I switch which join is shown, it follows suit.
If I don't use any of the joins, I can do a where just fine with one of the chemical fields. My question is why does having a join seem to break that particular With statement and is there a way to fix it? I get the feeling that it's something to do with the With statement doing some kind of behind the scenes join, but I haven't been able to find any specifics..
Thanks,
Forgot to mention I had tried Eager Loading Constraints before as well.
$chemicals = $app->Chemical->with(
array(
'Company',
'Room',
'Measurement',
'Location' => function ($query) {
$query->where('location', '=', 'FLAMCAB');
}
))
//->where('company', '=', 'FISHER')
->get();
Using above without the where commented out gives me the message that the chemicals table does not have a column called company, which is true.
Not using it, I do get results:
As you can see it does filter the locations table, but it does only that, it doesn't filter chemicals by that, which is what I'm trying to do.
If you want to filter Chemicals based on their related Locations, you can combine constraining eager loads with querying relationship existence:
$filter = function ($query) {
$query->where('location', '=', 'FLAMCAB');
};
$chemicals = $app->Chemical->with(
array(
'Company',
'Room',
'Measurement',
'Location' => $filter
)
->whereHas('location', $filter)
->get();
I'm not sure how efficient this is though - you might want to check out the resulting query. Another option is to simply filter the collection using Collection filters after you run the query.
This question: Complex query with filters with Eloquent might also help you.

CakePHP 3 - How to match on indirect associations? [duplicate]

I have a belongsToMany association on Users and Contacts.
I would like to find the Contacts of the given User.
I would need something like
$this->Contacts->find()->contain(['Users' => ['Users.id' => 1]]);
The cookbook speaks about giving conditions to contain, custom finder methods and sing through association key, but I did not find out how to put these together.
Use Query::matching() or Query::innerJoinWith()
When querying from the Contacts table, then what you are looking for is Query::matching() or Query::innerJoinWith(), not (only) Query::contain().
Note that innerJoinWith() is usually preferred in order to avoid problems with strict grouping, as matching() will add the fields of the association to the select list, which can cause problems as they are usually not functionally dependent.
See Cookbook > Database Access & ORM > Query Builder > Filtering by Associated Data
Here's an example using your tables:
$this->Contacts
->find()
->innerJoinWith('Users', function(\Cake\ORM\Query $q) {
return $q->where(['Users.id' => 1]);
});
This will automatically add the required joins + conditions to the generated query, so that only those contacts are being retrieved that are associated to at least one user with the id 1.
Target the join table
In case you'd have a manually set up many to many association via hasMany and belongsTo, you can directly target the join table:
$this->Contacts
->find()
->innerJoinWith('ContactsUsers', function(\Cake\ORM\Query $q) {
return $q->where(['ContactsUsers.user_id' => 1]);
});
Including containments
In case you actually want to have all the associations returned in your results too, then just keep using contain() too:
$this->Contacts
->find()
->contain('Users')
->innerJoinWith('Users', function(\Cake\ORM\Query $q) {
return $q->where(['Users.id' => 1]);
});
That would contain all users that belong to a contact.
Restricting containments
In cases where you have multiple matches, and you'd wanted to contain only those matches, you'd have to filter the containment too. In this example it doesn't make much sense since there would be only one match, but in other situations it might be useful, say for example if you'd wanted to match all contacts that have active users, and retrieve the contacts including only the active associated users:
$this->Contacts
->find()
->contain(['Users' => function(\Cake\ORM\Query $q) {
return $q->where(['Users.active' => true]);
}])
->innerJoinWith('Users', function(\Cake\ORM\Query $q) {
return $q->where(['Users.active' => true]);
})
->group('Contacts.id');
Given that there could be duplicates, ie multiple active users for a single contact, you'll probably want to group things accordingly, in order to avoid retrieving duplicate contact records.
Deep associations
You can also target deeper associations that way, by using the dot notated path syntax known from Query::contain(). Say for example you had a Users hasOne Profiles association, and you want to match only on those users that want to receive notifications, that could look something like this:
->innerJoinWith('Users.Profiles', function(\Cake\ORM\Query $q) {
return $q->where(['Profiles.receive_notifications' => true]);
})
This will automatically create all the required additional joins.
Select from the other table instead
With these associations and your simple requirements, you could also easily query from the other side, ie via the Users table and use just Query::contain() to include the associated contacts, like
$this->Users
->find()
->contain('Contacts')
->where([
'Users.id' => 1
])
->first();
All the contacts can then be found in the entities contacts property.
While #ndm answer got me where I needed to with a similar issue, solution needed a little tweak.
Problem was getting users filtered by data on the joinTable adding matching didn't return the users. So this is what I ended up with, hope it'll help someone.
$this->Contacts
->find()
->contain('Users', function(\Cake\ORM\Query $q) {
return $q->matching('ContactsUsers', function(\Cake\ORM\Query $q) {
return $q->where(['ContactsUsers.some_field' => 1]);
}
});
This got me Contacts with Users who has some_field set to 1 in association table. Or am I overcomplicating and there is a better solution?

Laravel4, eager loading is not working

I'm trying to get "reviews" from my database where the user is from MX (for example) so, I have this:
Review::with(array('user' => function($query)
{
$query->where('country_code', '=', 'MX');
}))
->with('user.country','pictures')
->where('shop_id',Input::get('id'))
->orderBy('id','DESC'))
->get()
->toArray()
But seems to be like where('country_code', '=', 'MX') is not taken into account because retrieve all reviews and I just want the reviews written by the user from MX.
user and picture inside with are functions within my User model
country_code is a field from users table
The goal is: Just get the reviews written by a user from the country specified, and was testing something like this:
Review::where('shop_id',Input::get('id'))
->with('user.country','pictures')
->where('users.country_code','MX')
->orderBy('id','DESC'))
->get()
->toArray()
But is not working as well because says: Unknow column users.country_code in where....
You want to do a query that filters based on the relation. Eager loading constraints only constrain the records that come with your main result set.
Review::whereHas('users', function ($q)
{
$q->where('country_code', 'MX');
})
->with('user','pictures')
->where('shop_id',Input::get('id'))
->orderBy('id','DESC'))
->get();
The whereHas is saying ... give me only Reviews that have a User with country_code=MX.
Reference
Laravel Docs - Eloquent - Querying Relations
If you want reviews written by a certain user or group of users you don't want to use eager loading. Try this:
Review::with(array('user', 'pictures'))
->join('users', 'reviews.user_id', '=', 'users.id)
->where('users.country_code', '=', 'MX')
->where('reviews.shop_id', Input::get('id'))
->orderBy('reviews.id', 'DESC'))
->get();

Laravel 4 - Eloquent way to attach a where clause to a relationship when building a collection

This may be a dupe but I've been trawling for some time looking for a proper answer to this and haven't found one yet.
So essentially all I want to do is join two tables and attach a where condition to the entire collection based on a field from the joined table.
So lets say I have two tables:
users:
-id
-name
-email
-password
-etc
user_addresses:
-address_line1
-address_line2
-town
-city
-etc
For the sake of argument (realising this may not be the best example) - lets assume a user can have multiple address entries. Now, laravel/eloquent gives us a nice way of wrapping up conditions on a collection in the form of scopes, so we'll use one of them to define the filter.
So, if I want to get all the users with an address in smallville, I may create a scope and relationships as follows:
Users.php (model)
class users extends Eloquent{
public function addresses(){
return $this->belongsToMany('Address');
}
public function scopeSmallvilleResidents($query){
return $query->join('user_addresses', function($join) {
$join->on('user.id', '=', 'user_addresses.user_id');
})->where('user_addresses.town', '=', 'Smallville');
}
}
This works but its a bit ugly and it messes up my eloquent objects, since I no longer have a nice dynamic attribute containing users addresses, everything is just crammed into the user object.
I have tried various other things to get this to work, for example using a closure on the relationship looked promising:
//this just filters at the point of attaching the relationship so will display all users but only pull in the address where it matches
User::with(array('Addresses' => function($query){
$query->where('town', '=', 'Smallville');
}));
//This doesnt work at all
User::with('Addresses')->where('user_addresses.town', '=', 'Smallville');
So is there an 'Eloquent' way of applying where clauses to relationships in a way that filters the main collection and keeps my eloquent objects in tact? Or have I like so many others been spoiled by the elegant syntax of Eloquent to the point where I'm asking too much?
Note: I am aware that you can usually get round this by defining relationships in the other direction (e.g. accessing the address table first) but this is not always ideal and not what i am asking.
Thanks in advance for any help.
At this point, there is no means by which you can filter primary model based on a constraint in the related models.
That means, you can't get only Users who have user_address.town = 'Smallwille' in one swipe.
Personally I hope that this will get implemented soon because I can see a lot of people asking for it (including myself here).
The current workaround is messy, but it works:
$products = array();
$categories = Category::where('type', 'fruit')->get();
foreach($categories as $category)
{
$products = array_merge($products, $category->products);
}
return $products;
As stated in the question there is a way to filter the adresses first and then use eager loading to load the related users object. As so:
$addressFilter = Addresses::with('Users')->where('town', $keyword)->first();
$users= $addressFilter->users;
of course bind with belongsTo in the model.
///* And in case anyone reading wants to also use pre-filtered Users data you can pass a closure to the 'with'
$usersFilter = Addresses::with(array('Users' => function($query) use ($keyword){
$query->where('somefield', $keyword);
}))->where('town', $keyword)->first();
$myUsers = $usersFilter->users;

Categories