Laravel model belongs to relationship loading issue - laravel

I have two models, Box and BoxLocations. Box has a hasMany relation to BoxLocations and BoxLocations has a belongsTo relationship to Box.
BoxLocations also has an attribute that is appended to the model that required a single piece of information from the Box relation.
I have noticed that when calling Box::with(['BoxLocations']->)all(); I see that the BoxLocations model is re-loading the Box relationship. This is happening for each BoxLocation (50 odd times)
Does laravel not track that the Box was already loaded from the initial Box::with(['BoxLocations']->)all(); request and then pass this to the BelongsTo relationship?
I am trying to optimise a web system and when the taken attribute is loaded (annoyingly its required every time its loaded as well) its causing 50 odd hits to the database for the same Box model it has already loaded.
If laravel does not do this - is there a better way in which to achieve the above?

Laravel uses eager loading when you use the with() method.
When accessing Eloquent relationships as properties, the relationship data is "lazy loaded". This means the relationship data is not actually loaded until you first access the property. However, Eloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the N + 1 query problem.
So if you do this:
$boxes = Box::with('BoxLocations')->get();
It will load the relations already, but lets' say you do this:
$boxes = Box::all();
foreach($boxes as $box)
{
echo box->boxlocation->name;
}
If you have 50 Boxes, this loop would run 51 queries.
But when you use the with method and eager load the relation, this loop will run only 2 queries.
You could also use Lazy Eager Loading and decide when you want to load the relationship

Related

Eager loading relations for only a subset of records in a Laravel collection

I'm working on a legacy Zend 1 project that's in the process of migrating to Laravel via the Strangler Pattern, and I'm using Eloquent for some of the database queries (though not all as they're still in the process of being migrated). Because Zend 1 can't work on any version of PHP higher than 7.1, it's stuck on the version of Eloquent from Laravel 5.8 until further notice.
I have one particular table containing navigation items, which has a nullable polymorphic relation for content items that uses a morph map to map the Eloquent models to the item types. One type, static doesn't have an Eloquent model assigned to it since it's just a container for other navigation items. Trying to eager load the relation on any items with the static type breaks it because it's not defined in the morph map, and setting it to null doesn't resolve the issue either.
Right now I load the items with a link type of static in a separate query and merge them with the query that gets the remaining navigation items, but this means there are two separate queries and that's not great for performance. Using a union doesn't resolve the issue because the eager load is a separate query from the one for the navigation items.
I know that it's possible to call load() on a collection after it's already been retrieved via a query in order to eager load a relation, and that it's also possible to constrain the eager load so that, for instance, a WHERE clause can be applied when loading the relation. What I'm trying to find is a means to ensure that the query only attempts to eager load the relation if the item type is not static. In other words, constraining not the query to get the relation, but excluding that record from the query so no attempt is made to retrieve the non-existent relation for that record.
Does anyone know if this is possible? One solution I can think of is to split off the items that aren't static in collections and apply load() there, but that's rather cumbersome and I was hoping there was a more elegant method.

Laravel Eager loading of a relationship bettween two models causes a relationship between other models return wrong results

I have two Eloquent models named Question and Answer with one to many relationship between them (one question has many answers). The hasMany relationship in Question.php is called answers.
I also have a User and a Company model with a many to many relationship between them which uses a pivot model, defined this way:
User.php
public function companies()
{
return $this->belongsToMany(Company::class)
->using(CompanyUser::class);
}
Company.php
public function users()
{
return $this->belongsToMany(User::class)
->using(CompanyUser::class);
}
When I retreive a question and lazy load its answers:
Question::find(58)->answers;
Everything is okay. The problem comes when I use eager loading:
Question::with('answers')->get();
Something strange happens. In the answers() relationship method of the Question model, I need to get the current user's first company in order to modify the relationship:
auth()->user()->companies->first();
Most of the users in my application have one company attaches to them, when using eager loading though auth()->user()->companies returns not one, but 134 companies even though in the database I have only 5 companies and the current user belongs to only one. When I dumped the contents of the auth()->user()->companies collection I saw that the first company model is exists 130 times and the other 4 companies are also included.
This happens only in the answers() method and only when using eager loading. Any ideas why?
Environment:
Laravel version: 6.20.6
PHP version: 8.0.1
Apache: 2.4.26
DB: 10.1.27-MariaDB
I think you need to get more information on the problem. What I normally do is insert ddd() and check the queries tab to see what queries Laravel is using to get the data. This might shed some more light on the problem.

Laravel Dynamic Eager Loading for Dynamic Relationships

Laravel Version: 5.5
PHP Version: 7+
Database Driver & Version: mysql 5.7+
Scenario:
I have a SaaS application that has flexible database structure, so its fields are bound to change, especially given it has a Json field (for any extra database structure to be created from client side of the application), including relationship based fields. so Account Table can have dynamically created employee_id field, and thus the need to access relationships dynamically
Problem:
I need to EagerLoad models based on this dynamic relationship. If I had something like this:
// Account Model
public function employee(){
return $this->belongsTo(App\Employee);
}
it would be easy. But what I have is this:
public function modelBelongsTo(){
return $this->belongsTo($dynamicClassName, $dynamicForeignKey);
}
Now if I eager load this, I'll get Account Model instance with related Employee on key modelBelongsTo. This is how Eloquent Names based on the function of eagerload. But after this I cannot use this function again to eagerload a second model because it'll just overwrite results on modelBelongsTo key.
Possible Solution Directions:
1) Can I Somehow change laravel's process to use a name I provide?
or
2) Can I write functions on the fly to overcome this so I'll write employee function on the fly?
or
3) Worst Case Scenario: I iterate over all records to rename their keys individually because I am using a pagination, it wouldn't that big of a deal to loop over 10 records.
Us a morph relationship
define the various dynamic classnames say
Employee
Boss
Morph works by having the related key and the table name stored in the parent table, it means to relate them you have to use a join or an orm and you cant have foreign key constraint on it as it links to different tables.
then have your account have morphs where
we have
Account
as top class
then we have
EmployeeAccount, BossAccount
which have their relation to boss and employee
then in Account have morphto relation call it specificAccount()
to which in its child morphs have the morph relation to Account
then add it to $with so to eager load them so when fetching account you could simply do
$account ->specificAccount
to get its morph child. which is nullable
This is totally dynamic such that if you have other classes in future you can just add and add the morph relationship. This may be applied to any reflection or runtime evaluated and loaded classes/code though it is not advisable to do this, as you can always edit code to create new functionality without affecting previous.

How to use eager loading with custom query builder in Laravel

I am new to Laravel 5.4 and working on some query manipulation. Now I have created an query using query builder which is shown below:
$view = DB::table('blocks')
->leftjoin('programmes', 'blocks.programme_id', '=', 'programmes.id')
->select('blocks.id', 'blocks.programme_id', 'blocks.name', 'blocks.colour', 'blocks.year', 'programmes.title AS programme');
I have two more table "dates" and "modules". Each dates as well as module belongs to blocks.
Now I want to fetch all blocks with programmes, dates and modules. I know i can use with() method to get all of these. But as per my knowledge on Laravel, I can use with() method only if I have model file of each table and have relationship between them.
But do not want to use model and define relationship between them. I just want to know How can I get block data with programmes, dates and modules without creating model and defining relationship betwen them in model? Is there any other way to use with() method without model?
Block dates and modules are conditional, Sometime I dont want get data of this table with block.
Please help me on this.
You can't do it automatically. Eager loading is only for Eloquent model so you cannot use it with query builder. However in most cases you can use Eloquent also for getting more complicated queries (you can also use joins when using Eloquent) so you will be able to use eager loading.
But if you don't want to use Eloquent at all, obviously you will need to create some custom mechanism for eager loading.

Load all relations and their children in Laravel

I have a rather complex structure that contains multiple relations. If my relations are defined this way, how can I load all of them in one call?
Model
(has many) ChildModels1
Child1a
Child1b
...
(has many) ChildModels2
Child2a
Child2b
...
(has many) ChildModels3
Child3a
Child3a
Child3aa
Child3ab
...
I'm able to do the following:
$entity = Entity::find($id)->load('ChildModels1', 'ChildModels2', 'ChildModels3');
But I'm not sure how to load all the child relations too.
This can be achieved with Eager Loading:
Entity::where('id', $id)->with('relation1.subrelation1', 'relation1.subrelation2', 'relation2.subrelation1', 'relation2.subrelation2')->get();
When accessing Eloquent relationships as properties, the relationship
data is "lazy loaded". This means the relationship data is not
actually loaded until you first access the property. However, Eloquent
can "eager load" relationships at the time you query the parent model.
Eager loading alleviates the N + 1 query problem.
You can read more about this in the Laravel documentation on eager loading.

Resources