How do I prevent fetching data multiple times inside a Repository? - laravel

My EloquentUserRepository (concrete implementation) has some methods like getCompanies($userId) and getProfile($userId), for example.
In both cases, before returning stuff, they fetch the user, like so:
$user = User::find($userId);
So, when I need to call those two methods in the same request, the data is fetched twice.
Am I missing something or am I using repositories wrongly?

What if you approached it from the relationship side instead? I'm assuming that Companies live in a different table. So could you acquire the companies with something like this?
<?php
class User extends Model {
protected $table = 'users';
protected $fillable = ['name', 'email', 'avatar'];
protected $hidden = ['password', 'remember_token'];
public function companies()
{
return $this->hasMany('App\Company', 'user_id');
}
}
Then you could find the companies with:
$user = User::find($userId)->load('companies')->get();
and you'd already have the companies loaded.
If the profile is in a different table you could do a similar relationship, like this:
<?php
class User extends Model {
protected $table = 'users';
protected $fillable = ['name', 'email', 'avatar'];
protected $hidden = ['password', 'remember_token'];
public function profile()
{
return $this->hasOne('App\Profile', 'user_id');
}
}
I'm not certain if this is exactly what you are looking for, but it's the approach I like to use when finding items that are related to a particular model.

Related

Laravel 9 - specular model relations not working

i can't figure out why my eloquent relationship is not working.
I have the grid and details tables related (es. grid.id = details.order_id). This works for first 2 tables but not for other 2 that are almost a copy of the first.
Working relationship code :
OrderGrid model:
use HasFactory;
protected $table = 'orders_grid';
protected $fillable = [
'user_id',
// more data
];
public function order_detail()
{
return $this->hasMany('App\Models\OrderDetail');
}
OrderDetail model:
use HasFactory;
protected $table = 'orders_detail';
protected $fillable = [
'order_id',
// more data
];
public function order_grid()
{
return $this->belongsTo('App\Models\OrderGrid', 'order_id', 'id');
}
In controller
$data_details = OrderDetail::where('order_id', $id)->get();
in view
$data_details->first()->order_grid->id
// so i can get the ID from the related table
Now i have two more very similar models which relations are not working:
OrderGrid model 2
use HasFactory;
protected $table = 'orders_grid_jde';
protected $fillable = [
'total_order',
// more data
];
public function detail_jde()
{
return $this->hasMany('App\Models\OrderDetailJde');
}
OrderDetail model 2 :
use HasFactory;
protected $table = 'orders_detail_jde';
protected $fillable = [
'order_jde_id',
];
public function grid_jde()
{
return $this->belongsTo('App\Models\OrderGridJde', 'order_jde_id', 'id');
}
In controller:
$data_details = OrderDetailJde::where('order_jde_id', $id)->get();
In view:
$data_details->first()->order_jde_id->id
This is not working, if i dump($data_details->first()) relations is empty array, not as in the screen shown before
Another weird behaviour on working relation: if i dump $data_details->first() in the controller i don't see the relation, but if i do it in view it shows as in the screen.
Sorry for the long post, tried to be understandble as possible
In your Controller call the relation like this:
$data_details = OrderDetailJde::where('order_jde_id', $id)->with("grid_jde")->get();
In view:
$data_details->first()
It will give all the detils.
It should work now.
In the view where you have the error, you are accessing the directly to the field named order_jde_id instead of the relationship grid_jde.
Tips:
You should eager load the relationships if you are planning to use these in the view.
$books = Book::with('author')->get();
https://laravel.com/docs/9.x/eloquent-relationships#eager-loading

Eloquent relationship mass assignment $guarded ignored

I have three table in DB, and I have used belongsTo for merging them. My Admins model looks like this :
class Admin extends Authenticatable
{
use HasFactory;
protected $guarded = ['status', 'shop_name', 'vendor_id', 'id'];
protected $guard = 'admin';
public function getVendorDetails(){
return $this->belongsTo(Vendor::class, 'vendor_id','vendor_id');
}
public function getVendorBusinessDetails(){
return $this->belongsTo(VendorBusinessDetails::class, 'vendor_id','vendor_id');
}
}
I need to prevent shop-name field to be saved through form. this field is in the table name : vendor_business_detals (getVendorBusinessDetails).But in this case $guarded only works for Admins table and ignores another relationship tables and fields of them such as shop_name. In short I am struggling to find the way to add $guarded data for VendorBusinessDetails. My controller looks like below:
$business_data = $request->only(['shop_name', 'shop_address', 'shop_phone']);
//dd($business_data);
$myModel = Admin::find($id);
//business table update
$myModel->getVendorBusinessDetails()->update($business_data);
VendorBusinessDetails model:
class VendorBusinessDetails extends Model
{
use HasFactory;
protected $guarded = ['shop_name'];
protected $table = 'vendors_business_details';
}
I don't know why $guraded=['shop_name'] is ignored. Any help would be highly appreciated.
$myModel->getVendorBusinessDetails()->update($business_data);
If you use "getVendorBusinessDetails" as a method, the model isn't initialize. Only the SQL update command runs.
$myModel->getVendorBusinessDetails->update($business_data);
Using "getVendorBusinessDetails" as a field initializes the model. So, it works as you expected.

Laravel : Can appends attributes lead to a n+1 problem?

Introduction
I start using appends attributes within models during fall 2019. At start, it was simply to run function that will join two column, but now, I use them to make some analysis on certain models before sending it to the frontend. Now, I'm suspecting this to lead to a n+1 problem. So, let check on some use of the appends attributes.
Basic usage
<?php
namespace App;
class User extends Authenticatable
{
protected $dates = ['deleted_at'];
protected $table = 'users';
protected $appends =
[
'full_name',
];
protected $fillable =
[
'username',
'first_name',
'last_name',
];
getFullNameAttribute()
{
return $this->first_name.' '.$this->last_name;
}
}
So, that let you append the full name, we know that, but under the hood?
$user = User::find($id);
Using appends with the above model retrieve is almost incensitive for any application. If we push it a little bit, what really happen?
$users = User::get();
Now, is the query to the database is the same as if we don't append anything? In other word, is the appending happen during the query to DB or after? Whatever, you need the full name and if it's not in the Database or the server, you will do it in the frontend, so that need to happen anyway. Which of the three is the fastest is for another question.
A bit more complexe usage
namespace App;
class User extends Authenticatable
{
protected $dates = ['deleted_at'];
protected $table = 'users';
protected $appends =
[
'full_name',
'last_post',
];
protected $fillable =
[
'username',
'first_name',
'last_name',
];
protected $hidden =
[
'password',
'remember_token',
];
public function posts()
{
return $this->hasMany('App\Post', 'user_id');
}
getFullNameAttribute()
{
return $this->first_name.' '.$this->last_name;
}
getLastPostAttribute()
{
return $this->posts()->last()
}
}
Now it's obvious that if I do $users->get(); it will result into a n+1 problem. But if I do this $users->with('posts')->get(); Do I have the n+1 problem or worst?
If your not use with n+1 problem
The n+1 problem is when you create a query that will need to make the initial call to DB and one call for each object into the array. $users->get() will get all users and then, since I append the last_post, since post where not retrieved, it will make a DB call for each of the models.
What I want to know, it's in the second case $users->with('posts')->get(), when Laravel will append last_post, will it know that it already have all the posts? So it doesnt need to load them a second time for each users?
So this attribute will cause n+1 queries no matter what you preload
getLastPostAttribute()
{
return $this->posts()->last()
}
By accessing posts() as a method you are creating a new query builder instance.
Replace it with return $this->posts->last() and then you can preload you posts relationship and avoid your extra queries
If you are unsure if you are getting n+1 issues you can require this great package from beyondcode beyondcode/laravel-query-detector which will inform you of any n+1's you have.
https://github.com/beyondcode/laravel-query-detector
To fix your n+1...
$users = Users::with(['posts'])->get();
public function getFullNameAttribute()
{
return $this->first_name.' '.$this->last_name;
}
public function getLastPostAttribute()
{
return $this->posts->last(); //update to not use builder instance
}

Laravel: Multiple tables in one model

I have the following model for Users:
class User extends Authenticatable
{
use Notifiable;
protected $table = 'login_info';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function getDashboards()
{
return \DB::table('dashboard')
->select('type')
->where('id', Auth::id())
->orderBy('column', 'asc')
->get();
}
}
Users have different information in many tables
user info like name, office, dashboard,2FA etc
Is the way I do it now "best practice" (like the getDashboards function) for getting information from different tables?
Or should I create a model for each of the tables and then "join them" (hasMany, belongsToMany, and so on) for each of the tables?
EDIT:
I am now using models, but the result of the query is always an empty array.
class Dashboard extends Model
{
protected $table = 'dashboard';
public function user()
{
return $this->belongsTo(User::class,'user_id','id');
//user_id
}
}
user_id is the id of the user which is used in the login_info table.
And in the User class I have:
public function dashboards()
{
return $this->hasMany(Dashboard::class,'id','user_id');
}
In the login controller I have:
$user = \App\User::find(1);
$user->dashboards;
Anyone see what the problem could be?
Thanks for any help!
public function dashboards()
{return $this->hasMany(\App\Dashboard::class);
}
And in your Dashboard Model you do it this way
protected $casts = [
'user_id' => 'int',
];
public function user()
{
return $this->belongsTo(\App\User::class);
}
The more Laravel way is to rather created the related Dashboard model and use the eloquent relationships, and harness the features of the ORM. Nothing wrong to include an orderBy on the relationship if you always need ordering on that column.
class User extends Authenticatable
{
public function dashboards()
{
return $this->hasMany(Dashboard::class)
->orderBy('column', 'asc');
}
}
class Dashboard extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
You do not have to do anything in the model! Just refer to the model in the controller, for example:
User::where('id', Auth::id())->pluck('type');

Automatic eager loading?

Rather than doing something like this (which I do dozens of times across the site):
$posts = Post::with('user')
->with('image')
->get();
Is it possible to automatically call with('image') whenever with('user') is called? So in the end, I could do just:
$posts = Post::with('user')
->get();
And still eager load image?
Add the following in your model:
protected $with = array('image');
and that should do the trick.
The $with attribute lists relations that should be eagerly loaded with every query.
Here another solution that works like a charm !
class Post extends Model {
protected $table = 'posts';
protected $fillable = [ ... ];
protected $hidden = array('created_at','updated_at');
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function userImage()
{
return $this->belongsTo('App\Models\User')->with('image');
}
}
$posts = Post::with('userImage')->get();
Using this you can still use your user posts $posts = Post::with('user')->get(); whenever you don't want to make an additional call to retrieve images ..

Resources