Laravel Eager Loading Relation With Additional ->where() - php

This is my relation method in my model:
public function relation()
{
return $this->belongsTo('App\Table', 'table_2_id', 'table_2_id')->where('table_1_id', $this->table_1_id);
}
So, both the model above and Table2 have the same column, but there is the possibility of many entries, so I want to then filter on a second column that is shared by both tables, table_1_id.
It seems to work perfectly on the command line, but when eager loading the relationship is empty.
If I remove the additional ->where() from the relationship method then the eager loading works.
The way I am eager-loading is by doing
->with('relation.nestedRelation');
I've also tried
->with(['relation' => function ($q) {
$q->with('nestedRelation');
}])
I've just tried adding it as an attribute to see if that helped but still no joy.

You can use the whereColumn() function :
->whereColumn('table_1.table_1_id', 'table_2.table_1_id')
Or you can use that :
public function filterRelation(){
return $this->relation->where('table_1_id', $this->table_1_id);
}

Related

Laravel - Eager Loading BelongsToMany Relationship

I have one to many relationship between two entities/tables.
/**
* Get all of the products.
*/
public function products()
{
return $this->belongsToMany(Product::class)->select(
[
'products.id',
'products.title',
'products.sku',
'automation_products.automation_id as auto_id',
'display_order',
]
)->orderBy('display_order');
}
When I want eager load this relationship, it seems like there are duplicate queries running in the background. I use this code to eager load my relation:
$automation = \App\Models\Automation::with('products')->whereId(1)->get()->first();
dump($automation->products()->get());
dump($automation->products()->get());
dump($automation->products()->get());
is there something I am missing?
Thanks for the reply.
Eager loading load relation into model property.
You can access this property like $automation->products - no matter how many times she is called - query will be executed ONE time with eager load.
But, when you call like ->products()->get() - eloquent execute query because you tell "get() relation products() NOW"

Laravel with() works only for first record (only for record with id 1)

Imagin situation where in laravel User Eloquent model I have this kind of relationship functions:
public function serviceProvider(){
return $this->belongsTo('App\Http\Models\Users\ServiceProvider');
}
public function company(){
return $this->serviceProvider->company();
}
I have this kind of relationship in serviceProvider model:
public function company()
{
return $this->morphOne('App\Http\Models\Companies\Company', 'companyable');
}
Now in controller I write something like that:
User::where('status', '=', 1)->with('company')->get();
I know it sounds crazy but it only works for record where user's id = 1, for the rest of the record it just returns company : null
I have no idea what is going on here, Maybe I am doing something wrong? what is the problem?
If I write something like that: $user->company, it works, It works for each user, I mean for
$user = User::where('id', '=', "any id")->first;
so problem is with with()
I'm a little surprised this is working at all to be fair.
Since your User a relationship for serviceProvider set up and that has a relationship for company you don't need to have a company relationship in User.
If you want to eager/lazy load nested relationships you can just use dot notation e.g.
User::with('serviceProvider.company')->where('status', 1)->get();
Lazy load docs (scroll down to Nested Eager Loading)

Eager load ONE from Many to Many relationship

I have a many to many relationship for users and roles. A user can have multiple roles, but I only want with to grab the FIRST role.
Consider the following code:
User::with('roles')->get()
Works great for all roles, but I only want the first role.
I've set this up in my model but doesn't work:
public function role()
{
return $this->roles()->first();
}
How do I load with for only the first result?
You should be able to call first directly on the eager loaded relationship like this:
User::with(['roles' => function ($query) {
$query->first();
})->get();
first() actually executes the query and returns the results as a collection. Relationships must return a query builder, which can then be chained or executed, so using first() in a relationship won't work.
UPDATE
I realised you want to use role in with, so you need to create a relationship to do that. Create a new relationship on your User model (you can use any limit described in the docs, not just oldest()):
public function role()
{
return $this->hasOne('App\Role')->oldest();
}
And then you can use it in with:
$users = User::with('role')->get();

Laravel relationship manytomany paginate issue

I have 3 tables / models
User (has an id)
Articles (has an id )
UserArticles (has an id, article_id and user_id)
I am a little confused on how I would set up the relationship so that I will be able to get all articles connected to a user so I can set up a call like so in my controller:
$articles = User::find(Auth::user()->id)->articles->paginate(20);
I figured this was a manytomany relationship so I am playing around with this code inside the User model:
public function articles()
{
return $this->belongsToMany('App\Article', 'user_saved_articles', 'user_id', 'article_id');
}
Now this works, as long as I don't call paginate() on the controller function I'd like to use above. Here is where my real issue lies now, so it works with
$articles = User::find(Auth::user()->id)->articles;
but with this:
$articles = User::find(Auth::user()->id)->articles->paginate(20);
it comes up with the error:
FatalErrorException in UserController.php line 217:
Call to undefined method Illuminate\Database\Eloquent\Collection::paginate()
I can't figure out why I can't paginate on this, as I can with all my other queries.
If you call an eloquent relation as an attribute User::find(Auth::user()->id)->articles it will automatically execute the SQL, and return a Collection. The error is telling you that you have a Collection, which you can't call paginate() on.
If you want to reference the relationship to add more statements you need to call it as a function User::find(Auth::user()->id)->articles(). This will return a QueryBuilder instance that you can add statements to, and/or paginate.
So this should work:
$articles = User::find(Auth::user()->id)->articles()->paginate(20);

Laravel, How to use where conditions for relation's column?

I'm using Laravel and having a small problem with Eloquent ORM.. I can get this working simply with SQL query using a JOIN but I can't seem to get it working with Eloquent!
This is what I want, I have two tabels. one is 'Restaurants' and other is 'Restaurant_Facilities'.
The tables are simple.. and One-To-One relations. like there is a restaurant table with id, name, slug, etc and another table called restaurant_facilities with id, restaurant_id, wifi, parking, etc
Now what I want to do is.. load all restaurants which have wifi = 1 or wifi = 0..
How can i do that with Eloquent ? I have tried eager loading, pivot tables, with(), collections() and nothing seems to work!
The same problem I have for a Many-To-Many relation for cuisines!
I have the same restaurant table and a cuisine table and a restaurant_cuisine_connection table..
but how do I load all restaurants inside a specific cuisine using it's ID ?
This works.
Cuisine::find(6)->restaurants()->get();
but I wanna load this from Restaurant:: model not from cuisines.. because I have many conditions chained together.. its for a search and filtering / browse page.
Any ideas or ways ? I've been struggling with this for 3 days and still no answer.
Example Models :
class Restaurant extends Eloquent {
protected $table = 'restaurants';
public function facilities() {
return $this->hasOne('Facilities');
}
}
class Facilities extends Eloquent {
protected $table = 'restaurants_facilities';
public function restaurant() {
return $this->belongsTo('Restaurant');
}
}
PS :
This seems to be working.. but this is not Eloquent way right ?
Restaurant::leftJoin(
'cuisine_restaurant',
'cuisine_restaurant.restaurant_id',
'=', 'restaurants.id'
)
->where('cuisine_id', 16)
->get();
Also what is the best method to find a count of restaurants which have specific column value without another query ? like.. i have to find the total of restaurants which have parking = 1 and wifi = 1 ?
Please help on this.
Thank you.
I don't see anything wrong with doing the left join here, if you have to load from the Restaurant model. I might abstract it away to a method on my Restaurant model, like so:
class Restaurant extends Eloquent {
protected $table = 'restaurants'; // will be default in latest L4 beta
public function facility()
{
return $this->hasOne('Facility');
}
// Or, better, make public, and inject instance to controller.
public static function withWifi()
{
return static::leftJoin(
'restaurant_facilities',
'restaurants.id', '=', 'restaurant_facilities.restaurant_id'
)->where('wifi', '=', 1);
}
}
And then, from your routes:
Route::get('/', function()
{
return Restaurant::withWifi()->get();
});
On the go - haven't tested that code, but I think it should work. You could instead use eager loading with a constraint, but that will only specify whether the facility object is null or not. It would still return all restaurants, unless you specify a where clause.
(P.S. I'd stick with the singular form of Facility. Notice how hasOne('Facilities') doesn't read correctly?)
I stumbled across this post while trying to improve my REST API methodology when building a new sharing paradigm. You want to use Eager Loading Constraints. Let's say you have an api route where your loading a shared item and it's collection of subitems such as this:
/api/shared/{share_id}/subitem/{subitem_id}
When hitting this route with a GET request, you want to load that specific subitem. Granted you could just load that model by that id, but what if we need to validate if the user has access to that shared item in the first place? One answer recommended loading the inversed relationship, but this could lead to a confusing and muddled controller very quickly. Using constraints on the eager load is a more 'eloquent' approach. So we'd load it like this:
$shared = Shared::where('id', $share_id)
->with([ 'subitems' => function($query) use ($subitem_id) {
$query->where('subitem_id', $subitem_id)
}]);
So where only want the subitem that has that id. Now we can check if it was found or not by doing something like this:
if ($shared->subitems->isEmpty())
Since subitems is a collection (array of subitems) we return the subitem[0] with this:
return $shared->subitems[0];
Use whereHas to filter by any relationship. It won't join the relation but it will filter the current model by a related property. Also look into local scopes to help with situations like this https://laravel.com/docs/5.3/eloquent#local-scopes
Your example would be:
Restaurant::whereHas('facilities', function($query) {
return $query->where('wifi', true);
})->get();
Restaurant::whereHas('cuisines', function($query) use ($cuisineId) {
return $query->where('id', $cuisineId);
})->get();
To achieve the same thing with local scopes:
class Restaurant extends Eloquent
{
// Relations here
public function scopeHasWifi($query)
{
return $query->whereHas('facilities', function($query) {
return $query->where('wifi', true);
});
}
public function scopeHasCuisine($query, $cuisineId)
{
return $query->whereHas('cuisines', function($query) use ($cuisineId) {
return $query->where('id', $cuisineId);
});
}
}
For local scopes you DO NOT want to define them as static methods on your model as this creates a new instance of the query builder and would prevent you from chaining the methods. Using a local scope will injects and returns the current instance of the query builder so you can chain as many scopes as you want like:
Restaurant::hasWifi()->hasCuisine(6)->get();
Local Scopes are defined with the prefix scope in the method name and called without scope in the method name as in the example abover.
Another solution starring whereHas() function:
$with_wifi = function ($query) {
$query->where('wifi', 1);
};
Facilities::whereHas('restaurant', $with_wifi)
Nice and tidy.
Do you absolutely have to load it from the Restaurant model? In order to solve the problem, I usually approach it inversely.
Facilities::with('restaurant')->where('wifi' ,'=', 0)->get();
This will get all the restaurant facilities that match your conditions, and eager load the restaurant.
You can chain more conditions and count the total like this..
Facilities::with('restaurant')
->where('wifi' ,'=', 1)
->where('parking','=', 1)
->count();
This will work with cuisine as well
Cuisine::with('restaurant')->where('id','=',1)->get();
This grabs the cuisine object with the id of 1 eager loaded with all the restaurants that have this cuisine

Categories