Laravel relationship eager loading limiting all results instead of model specific - php

I have a nested relationship like:
Model One has many Model Twos which has many Model Threes.
The aim is to get all Model Ones and all their Model Twos and for the each of the Model Twos only get the latest Model Three.
I have tried:
return ModelOne::with(['modelTwos.latestModelThree'])
->paginate();
But this is only giving the Model Three for the last Model Two of the last Model One.
All other Model Twos have no Model Three in the response.
The method for latestModelThree on the Model Two class is:
public function latestModelThree()
{
return $this->hasMany(ModelThree::class)
->latest()
->limit(1);
}

Changing larestModelThree() relationship to hasOne() and removing the limit should do the trick:
public function latestModelThree()
{
return $this->hasOne(ModelThree::class)
->latest();
}
It's a little hacky, and don't solve the problem if you need N items of that relationship instead of a single item, but, solves your problem.

The answer by Elias Soares does work but causes major performance issues with large datasets.
A more performant solution is to use this package: https://github.com/staudenmeir/eloquent-eager-limit

Related

Can "for" relations in a laravel seeder use the child model?

I'm writing a seeder in which one model (ProductVariant) belongs to (many to one) other model (Product). For properly seeding all relations on the Product, I need to access the ProductVariant model. The Laravel documentation shows this is possible with the ->has() function, but because of the nature of this relationship I need to use a ->for(), which doesn't seem to work in the same way.
Below is the snipped of code I'm talking about and how I would expect it to work:
ProductVariant::factory()
->for(
Product::factory()
->state(function (array $attributes, ProductVariant $productVariant) {
dd(($attributes, $productVariant);
})
)->create();
Is there another way to do what I need it to do or am I missing something?
Documentation I'm referring to: https://laravel.com/docs/9.x/database-testing#has-many-relationships

Laravel Eager Loading, multiple same hasOne relations

I have 2 simple models. First, one is called Builds and the second one is called SlotOptions. Each build can have like 5 assigned slots.
class BuildDB extends Model
And has 5 such relations slot1-5 and id changes to slot1-5_id
public function slot1()
{
return $this->hasOne('\App\SlotOptions', 'id', 'slot1_id');
}
In the controller I call it such way;
BuildDB::with([ 'slot1', 'slot2', 'slot3', 'slot4', 'slot5'])->find(5);
\App\SlotOptions model doesn't contain any extra coding.
This generates 5 "same" queries. - atm the eager loading would work if I get a list of builds and each slot would have whereIn clause, is it possible to have it a one big wherein the clause, or does it require to change the DB schema.
It's not possible to optimize eager loading in this case.
I recommend that you change your database schema to a many-to-many relationship.
This design is more flexible, it allows you to easily add more slots in the future.
Create a pivot table named build_slot_option with these columns: build_id, slot_option_id
Add an additional column if you want to number/order the slots.
Then define a BelongsToMany relationship:
class BuildDB extends Model
{
public function slots()
{
return $this->belongsToMany(
SlotOptions::class, 'build_slot_option', 'build_id', 'slot_option_id'
);
}
}
BuildDB::with('slots')->find(5);

Relationships: how to get 'one to one' data after many to many consult

as the title says I'm trying to access to a row that it's related to a one to one relationship, I've tried with the method that is in my model but it only works when the result isnt a collection, it maybe needs a more complicated consult, do you have any ideas how to do it with eloquent
Empresa model
public function transferencias_recibidas()
{
return $this->belongsToMany('App\Transferencia_recibir', 'empresa_transferencia_recibir', 'empresa_id', 'transferencia_recibir_id');
}
Transferencia_recibir model (the inverse of a hasOne relation)
public function transferencia()
{
return $this->belongsTo('App\Transferencia');
}
This is what i get, a collection
This is what a need for each one of them
$a=$empresa->transferencias_recibidas->find(1)->transferencia
Thx for the help guys
The eloquent relationship are "lazy loaded", meaning they will only load their relationship data when you actually access them. The output you shown are expected because you did not accessed that relationship, but you can load that relationship in two ways:
access it when needed:
$empresa->transferencias_recibidas->first()->transferencia;
"Eager load" all the transferencia relationship
Empresa::with('transferencias_recibidas.transferencia')->where([ .. ])->get();
The second method alleviates the N + 1 query problem. Since the first method executes N queries to retrieve all relationship, instead of a query to retrieve all the relationship for the second method.
You may need to check eager loading section.

Laravel Polymorphic relationship getting different models

I have a Laravel model called "Area" which contains "Elements".
Elements can be different Models (in this case a FreetextElement and a CheckboxElement). The whole thing is hooked up with a polymorphic pivot table,
which contains the area_id, the element_id and the element_type.
The basic relationship works fine.
If I for example say:
$area->freetextElements
Then I get all the freetextElements that are attached to that particular area.
My issue is that I'd like a relationship function, which gets all the elements that are attached to the area, regardless of their model.
Here are the areas relations:
public function freetextElements()
{
return $this->morphedByMany(ElementFreetext::class, 'element', 'coaching_element_area_element');
}
public function checkboxElements()
{
return $this->morphedByMany(ElementCheckbox::class, 'element', 'coaching_element_area_element');
}
//find a better solution for this
public function elements()
{
return array_merge( $this->freetextElements->all(), $this->checkboxElements->all());
}
The last function "elements" is just to illustrate what I'm trying to achieve.
Any suggestions? Thanks in advance.
Best Regards
So I have found a solution to this issue. It's not the cleanest solution.
So I'm still open to any additional feedback, but I thought I leave this here for other people.
I simply created a model for the pivot table entries. Area->Elements is the relationship to the pivot table entries and That pivot table model is related to the individual elements via it's own relation. Now I can chain the relation by saying: Area->Elements->Element. It's not optimal, but it gets the job done.

laravel Eloquent join and Object-relationship mapping

Ok so i'm kind of newish to eloquent and laravel (not frameworks tho) but i hit a wall here.
I need to perform some queries with conditions on different tables, so the eager load (::with()) is useless as it creates multiples queries.
Fine, let use the join. But in that case, it seems that Laravel/Eloquent just drops the concept of Object-relationship and just return a flat row.
By exemple:
if i set something like
$allInvoicesQuery = Invoice::join('contacts', 'contacts.id', '=', 'invoices.contact_id')->get();
and then looping such as
foreach ($allInvoicesQuery as $oneInvoice) {
... working with fields
}
There is no more concept of $oneInvoice->invoiceFieldName and $oneInvoice->contact->contactFieldName
I have to get the contacts fields directly by $oneInvoice->contactFieldName
On top of that the same named columns will be overwrited (such as id or created_at).
So my questions are:
Am i right assuming there is no solution to this and i must define manually the field in a select to avoid the same name overwritting like
Invoice::select('invoices.created_at as invoice.create, contacts.created_at as contact_create)
In case of multiple joins, it makes the all query building process long and complex. But mainly, it just ruins all the Model relationship work that a framework should brings no?
Is there any more Model relationship oriented solution to work with laravel or within the Eloquent ORM?
Instead of performing this join, you can use Eloquent's relationships in order to achieve this.
In your Invoice model it would be:
public function contact(){
return $this->belongsTo('\App\Contact');
}
And then of course inside of your Contact model:
public function invoices(){
return $this->hasMany('\App\Invoice');
}
If you want to make sure all queries always have these active, then you'd want the following in your models:
protected $with = ['Invoice']
protected $with = ['Contact'];
Finally, with our relationships well defined, we can do the following:
$invoices = Invoice::all();
And then you can do:
foreach($invoices as $invoice)[
$invoice->contact->name;
$invoice->contact->phone;
//etc
}
Which is what I believe you are looking for.
Furthermore, you can find all this and much more in The Eloquent ORM Guide on Laravel's site.
Maybe a bit old, but I've been in the same situation before.
At least in Laravel 5.2 (and up, presumably), the Eloquent relationships that you have defined should still exist. The objects that are returned should be Invoice objects in your case, you could check by dd($allInvoiceQuery); and see what the objects are in the collection. If they are Invoice objects (and you haven't done ->toArray() or something), you can treat them as such.
To force only having the properties in those objects that are related to the Invoice object you can select them with a wildcard: $allInvoicesQuery = Invoice::select('invoices.*')->join('contacts', 'contacts.id', '=', 'invoices.contact_id')->get();, assuming your corresponding table is called invoices.
Hope this helps.

Categories