Laravel Model Relations fetch automatically all other model relations too - laravel

I'm hoping someone can help me.
I have 3 models like User, Task and Subtask.
These are already linked via hasOne or hasMany.
Everything works fine.
Now I call the data via Task::where(..)->with(['user','subtask'])... and get the corresponding results.
The problem is that Subtask has a reference to User and I don't get the user information queried when I use the task model.
If I use the subtask model I get the user information.
How can I set up that all references to the queried models are also queried simultaneously from the database?

To return more relationships data at once, you can use the following mechanism:
$Data = $Task::where(...)
->with([
'user'=>function($userQuery){
// mechanism is recursive; you can extend it to infinity :)
$userQuery->with([
'other',
'relationships',
'here'=>function($hereQuery){ ... }
]);
},
'subTask',
'anotherRelationship' => function($anotherRelationship) {
$anotherRelationship->select('column');
$anotherRelationship->where('column2', 'whatever');
}
])
->get();
// dump data
dd($Data);
I don't know if you're looking for this -- if you want to load some relationships once the model is instantiated, you can append a magic variable $with inside your model code and specify which relationships you want to load:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $fillable = ['col1', 'col2', 'col3', ...];
// specify which relationships you want to load automatically
protected $with = [
'users',
'anotherRelationship'=>function($anotherRelationshipQuery){
$anotherRelationshipQuery->select( ... );
},
'andSomeOtherRelationship'
...
];
...
}
Now you no longer need to manually load the relationships when retrieving data. They're loaded automatically:
$Data = $Tast::where( ... )->get();
dd($Data);

Related

Laravel many to many and hasMany, N+1

I got a model Event that has many private classes
public function privateclasses()
{
return $this->hasMany(Privateclass::class);
}
This is working perfect
Model Privateclass and User is connected in table "privateclass_user" with foreign key "privateclass_id" and "user_id".
In model Privateclass.php:
public function users()
{
return $this->belongsToMany(User::class);
}
This is also working, but when I get the event the query count is high.
How do I Eager Load this?
$event = Event::with('users')->get(); <-- NOT WORKING
In short: I want all users connected to privateclass that belongs to an Event.
https://laravel.com/docs/9.x/eloquent-relationships#nested-eager-loading
To eager load a relationship's relationships, you may use "dot" syntax.
Looks like you need:
Event::with('privateclasses.users')
or:
Event::with([
'privateclasses' => [
'users'
]
])

Laravel eloquent for four tables

I'm new to Laravel. I am developing a project. and in this project I have 4 tables related to each other
-Users
-Orders
-OrderParcels
-Situations
When listing the parcels of an order, I want to get the information of that order only once, the user information of that order once again, and list the parcels as a table under it. so far everything ok. but I also want to display the status of the parcels listed in the table as names. I couldn't add the 4th table to the query. do you have a suggestion? I'm putting pictures that explain the structure below.
My current working code is
$orderParcels = Orders::whereId($id)
->with('parcels')
->with('users:id,name')
->first();
and my 'orders' model has method
public function parcels(){
return $this->hasMany(OrderParcels::class);
}
public function users(){
return $this->hasOne(User::class,'id','affixer_id');
}
Note[edit]: I already know how to connect like this
$orderParcels = DB::table('order_parcels as op')
->leftjoin('orders as o','op.orders_id','o.id')
->leftjoin('users as u','o.affixer_id','u.id')
->leftjoin('situations as s','op.status','s.id')
->select('op.*','o.*','u.name','s.situations_name')
->where('op.orders_id',$id)->get();
but this is not working for me, for each parcels record it returns me orders and user info. I want once orders info and once user info.
Laravel provides an elegant way to manage relations between models. In your situation, the first step is to create all relations described in your schema :
1. Model Order
class User extends Model {
public function parcels()
{
return $this->hasMany(OrderParcels::class);
}
public function users()
{
return $this->hasOne(User::class,'id','affixer_id');
}
}
2. Model Parcel
class Parcel extends Model {
public function situations()
{
return $this->hasOne(Situation::class, ...);
}
}
Then, you can retrieve all desired informations simply like this :
// Retrieve all users of an order
$users = $order->users; // You get a Collection of User instances
// Retrieve all parcels of an order
$parcels = $order->parcels; // You get a Collection of User instances
// Retrieve the situation for a parcel
$situations = $parcel->situations // You get Situation instance
How it works ?
When you add a relation on your model, you can retrieve the result of this relation by using the property with the same name of the method. Laravel will automatically provide you those properties ! (e.g: parcels() method in your Order Model will generate $order->parcels property.
To finish, in this situation where you have nested relations (as describe in your schema), you should use with() method of your model to eager load all the nested relation of order model like this :
$orders = Orders::with(['users', 'parcels', 'parcels.situations'])->find($id)
I encourage you to read those stubs of Laravel documentation :
Define model relations
Eager loading
Laravel Collection
Good luck !
Use join to make a perfect relations between tables.
$output = Orders::join('users', 'users.id', '=', 'orders.user_id')
->join('order_parcels', 'order_parcels.id', '=', 'orders.parcel_id')
->join('situations', 'situation.id', '=', 'order_parcels.situation_id')
->select([
'orders.id AS order_id',
'users.id AS user_id',
'order.parcels.id AS parcel_id',
'and so on'
])
->where('some row', '=', 'some row or variable')->get();

Make Hidden child's relation /Laravel Eloquent

Model structure:
AccessoryGroup (hasMany: accessories)
Accessory (belongsTo: accessory_group)
Get all accessory groups with accessories (with accesory_group)
In accessories I needed accessory_group relation, to create some custom attribute (appends)
But after usage I don't won't my Api to return relation from accessories->accessory_group
AccessoryGroup
::with([
'accessories' => function ($query) {
$accessory = $query->getRelated();
// Can I somehow use this $accessory relation for my problem
// something like $accessory->makeHidden('accessory_group'); - not working
},
'accessories.accessory_group',
])
->get();
when i add public $hidden = ['accesory_group']; in Accessory model I get what i want, but then it's always hidden (always need to use makeVisible)
From your question what I get is you don't want to return the relation in return to your API response. I'm not prefer to load the data from relation again if you have that in parent model so suggest you do that in controller part to build your business logic.
But specific to your current problem you can do like this
$accessoryGroups = AccessoryGroup::query()
->with([
'accessories',
'accessories.accessory_group',
])
->get();
// do your work here
// unset the relationship after your work complete
$accessoryGroups->each(function($accessoryGroup) {
$accessoryGroup->accessories->each(function($category) {
$category->unsetRelation('accessory_group');
});
});
// return api response

Laravel 5: How to use firstOrNew with additional relationship fields

I have a CMS that allows the user to save and create bike tours. Each bike tour also has categories, which are definined using Laravel's Many to Many relationship utilising an intermediary pivot table. At the point of saving a tour, we don't know if the tour is an existing one being edited, or a new one.
I think I should be using Laravel's firstOrNew method for saving the tour, and the sync method for saving categories. However, all the tutorials very simplistically just give the example of passing a single object to the function like so:
$tour = Tour::firstOrNew($attributes);
But what happens when my $attributes also contains extra stuff, like the categories which are linked to a relationship table, and which I will need to save in the next step? For example this very good tutorial gives the following example:
$categories = [7, 12, 52, 77];
$tour = Tour::find(2);
$tour->categories()->sync($categories);
But what happens if the category data is bundled with the data for the rest of the tour, and instead of using find I need to use firstOrNew to create the tour? Should I keep the categories in the $attributes while I instantiate the tour, then run the sync, then unset them before saving the tour, or...? Is there a better way to achieve this?
EDIT: To be clear, the $attributes variable in my example here is essentially the tour object data bundled together- just as the Laravel/Eloquent system would return it from the transaction using the belongsToMany method- with subequent modifications from the user). ie: here is a snapshot of what it contains:
array (
'id' => 1,
'uid' => '03ecc797-f47e-493a-a85d-b5c3eb4b9247',
'active' => 1,
'code' => '2-0',
'title' => 'Tour Title',
'url_title' => 'tour_title',
'distance_from' => 20,
'distance_to' => 45,
'price_from' => '135.00',
'price_to' => '425.00',
'created_at' => '2013-12-31 15:23:19',
'updated_at' => '2015-07-24 16:02:50',
'cats' => // This is not a column name!
array (
0 => 1,
1 => 7
),
)
All of these attributes are column names in my tours table, other than cats, which references another table via a hasMany relationship. Do I need to unset it manually before I can set this object class and save it with $tour->save?
I am looking for the cleanest most Laravel way to do it?
EDIT2: Here is the relationship defined in the Tours model:
class Tour extends Model
{
protected $guarded = [];
public function cats(){
return $this->belongsToMany('App\TourCategory', 'tour_cat_assignments', 'tour_id', 'cat_id');
}
}
you need to define $fillable property of your Tour model to tell eloquent which attributes to consider when using mass assignment so it will ignore categories related attributes silently. for ex.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tour extends Model {
protected $fillable = ['name'] //... other attributes which are part of this model only and laravel will consider only these attributes and ignore category related attributes which you can consider later use.
}
You can use firstOrCreate. The data actually gets persisted using this method.
$categories = [7, 12, 52, 77];
$tour = Tour::firstOrCreate($attributes)->cats()->sync($categories);
Got to make sure the fields are mass-assignable to be able to use the firstOrCreate method though. So either set the fieldnames in the $fillable property or put this in the Tour model:
protected $guarded = [];
Since you have mentioned "CMS" and "subsequent modifications from user", I guess that you are getting your attributes from a Form which means you are getting a Request object/collection.
If that is the case then you can try
$tour = Tour::firstOrCreate($request->except('cats'));
$categories = [];
foreach($request->get('cats') as $key=>$value){
$categories[] = $value;
}
$tour->cats()->sync($categories);
However, if your $attributes us constructed as an array (probably with some manipulations on form data) as per your EDIT then in that case you may try:
$tour = Tour::firstOrCreate(array_except($attributes, ['cats']);
$categories = [];
foreach($attributes['cats'] as $key=>$value){
$categories[] = $value;
}
$tour->cats()->sync($categories);
In any case, you must have the mass assignable fields declared in $fillable property in your model i.e. Tour.
Hope this helps.

Model returns relation´s dynamic attributes but null relation

I have set 2 models (Post and Category) with it´s proper relationships configured
class Post extends Model
{
protected $fillable = [
'title',
'excerpt',
'body',
'featured',
'published',
'category_id',
];
public function category()
{
return $this->belongsTo('App\Category');
}
}
class Category extends Model
{
protected $fillable = [
'name',
];
public function posts()
{
return $this->hasMany('App\Post');
}
}
And my Post´s storing method is
public function store(Request $request)
{
$post = Post::create($request->all());
return redirect('admin/posts');
}
The thing is, it´s actually working ok, it sets the category_id on the table and I can fetch all the dynamic data by using $post->category->name, but when I var_dump($post->relation) I get a null return.
I if create a new Post model, set all the attributes, save it and then associate the Category model (as documented on the official channel), it will return everything as expected.
For now, all I need is to fetch it´s dynamic attributes, and it´s working fine now, but I know I must be doing something wrong to get the null response. My concern is that it may be working fine now, but when the project gets larger I´ll probably face a bigger problem and I´ll have a lot of work to fix this issue.
The relation isn't there because you haven't loaded it. All it knows is the foreign key. It would be wildly inefficient if it grabbed all that information for you because it wouldn't always need all that. Think of instances where a single model could have many relationships, that would be many database calls for no reason.
If you need the relation, you can use $post->category. Since the relation is not yet loaded, it will get it for you when you do this.
Or you can eager load it by using the following $post->load('category') although this doesn't really benefit you because you are working with a single Post at this point. If you had a collection of Post objects, then you'd start seeing the benefits of using $posts->load('category') otherwise you end up with the n + 1 problem.
Consequently, if you use $post->load('category') and then var_dump($post), you should see that the relation is no longer null.

Resources