Paginate with Eloquent but without instantiation Models - laravel

We have two Models:
SimpleModel (id, country, code)
ComplexRelatedModel (id, name, address)
SimpleModel has many ComplexRelatedModel, then
class Product extends Model
{
protected $fillable = [
'name'
];
/* hasOne */
public function complexRelatedChild()
{
return $this->hasOne(self::class, 'parent_id', 'id');
}
}
If we do
$simples = SimpleModel
->with('complexRelatedChild')
->simplePaginate(100000 /* a lot! */);
And we need only do
foreach ($simples as $simple) {
echo $simple->complexRelatedChild->name;
}
Any ComplexChild has hydratated and ready. This takes a lot of memory in my case. And we need just one field without any funciton or feature of Model.
It's possible use some data field from related object or with eloquent this isn't possible?

Not sure I completely understand your question. You want to only load one field from the complexRelatedChild relation to keep memory limit down?
You could do:
$simples = SimpleModel::with(['complexRelatedChild' => function($query){
return $query->select(['id', 'name']);
})
->simplePaginate(100000);
Which can be simplified to:
$simples = SimpleModel::with('complexRelatedChild:id,name')
->simplePaginate(100000);
However if I were you, I would try to paginate less items than 100000.
Update:
You could use chunk or cursor functions to process small batches of SimpleModel and keep memory limit down.
SimpleModel::chunk(200, function ($simples) {
foreach ($simples as $simple) {
}
});
or
foreach (SimpleModel::cursor() as $simple) {
}
See the documentation for more information

Related

How to add to my Index/Show, so that it displays a table, from another table. (Laravel)

First post here, hope all is well with everyone! I am working on my senior project and struggling with this concept. Maybe it isn't possible, or it is and I'm thinking about it the wrong way.
I currently have this- This displays the student, with all its foreign keys in the other tables. In settings, there is a foreign key 'conversion_id.', I would like settings on the student call(code below) to ALSO display the conversion table, from the FK in settings.
$student = Student::with('studentIntroSurveys', 'settings', 'giftsSurveyResults',
'studentGiftSurveys', 'devotionals', )->get();
If this does not make sense, I am sorry. I am still l new to the language.
(Code to display students. It shows settings, but not the conversions within settings.)
$student = Student::with('studentIntroSurveys', 'settings', 'giftsSurveyResults',
'studentGiftSurveys', 'devotionals', )->get();
if (!$student) {
return response('No Data', 400);
} else {
return response($student);
}
I would like it to display this settings, but WITH the conversion_id table!!
What it displays =
settings: { setting_id: 4, student_id: 1, dark_mode: 1, conversion_id: 1,
notification_enabled: 1, notification_time: "08:00:00" } `
my has-one method =
public function settings()
{
$settings = $this->hasOne(Settings::class, 'student_id', 'student_id');
return $settings;
}
You can use the dot syntax for nested eager loading: 'settings.conversation' e.g.
$student = Student::with('studentIntroSurveys', 'settings.conversation', 'giftsSurveyResults', 'studentGiftSurveys', 'devotionals', )->get();
Alternatively, you could set up a belongsToMany relationship on the Student model and use settings as the pivot table:
public function conversations()
{
return $this->belongsToMany(Conversation::class, 'settings')
}
Just a few FYIs:
Your if statement is never going to return the 400 response as $student is always going to be a collection. You could instead do if ($student->isEmpty()).
I would also recommend changing the variable to $students as it will be a collection of students rather than a single student.
You can simple your settings relationship by removing the temporary variable and just returning the relationship:
public function settings()
{
return $this->hasOne(Settings::class, 'student_id', 'student_id');
}

Eager load for collections on the fly

I have this, user has many to many property via pivot property_users.
I am making somehow reusable classes in my webapp.
These are the models with their eager loading functions:
//User model
public function properties()
{
return $this->belongsToMany(Property::class, 'property_users', 'user_id', 'property_id');
}
//Property model
public function property_users()
{
return $this->hasMany(PropertyUser::class, 'property_id', 'id');
}
//PropertyUser model
public function user()
{
return $this->belongsTo(User::class);
}
//GetProperties class
public function handle()
{
return auth()->user()->properties()->get();
}
//somewhere in a feature
$properties = $this->run(GetProperties::class);
//this returns valid properties under the logged in user
I now need to get the chat_username in property_users that belongs to this user
I manage to make it work if I loop through the properties and then doing it on the fly.
$properties = $properties->map(function($property) {
$propertyUsers = $property->property_users()->get();
$chatUsername = null;
foreach($propertyUsers as $propertyUser) {
if($propertyUser->property_id == $property->id) {
$chatUsername = $propertyUser->chat_username;
}
}
return [
'name' => $property->name,
'id' => $property->id,
'chat_username' => $chatUsername
];
});
But I am trying to reduce query on loop to reduce hits especially when they are on multiple properties in the database.
The other way is that I can add the property_users in the eager loading under GetProperties class by updating it to:
$query = Property::query();
$query->with(['property_users']);
$query->whereHas('property_users', function($qry) {
$qry->where('user_id', Auth::user()->id);
});
$properties = $query->get();
return $properties;
But I do not want to rely on adding more eager loading to the original GetProperties class as the GetProperties will get fat and I do not really need those data (let's say adding property_invoices, property_schedules, etc but not really needing it in some area).
Rather, I want to do the eager loading on the fly but with a twist! This is how I would imagine it:
Collect all the ids from the properties, do the fetch using wherein and apply all the users to the properties in a single query. This way it will be even more beautiful.
Maybe something like this: (using the original GetProperties class)
$properties = $this->run(GetProperties::class);
//this does not work. The error is: Method Illuminate\Database\Eloquent\Collection::property_users does not exist.
$property->property_users = $properties->property_users()->get();
Would be great if someone can show me how to do it.
What about eager loading only the fields you actually need?
$query->with('property_users:id,user_id');
The model will not get fat, and you will not need to do separate queries in the loop.
It is documented in the official documentation: https://laravel.com/docs/5.8/eloquent-relationships#eager-loading , see Eager Loading Specific Columns
Edit: if you want to perform the query after the GetProperties class, you will need to collect all the ids and perform a second query. I honestly don't like this second approach, because it is far slower, less performant and I consider it less elegant then adding a single line in the GetProperties class, but it is gonna work:
$properties = $this->run(GetProperties::class);
$ids = $properties->pluck('id'); // Get all the ids as an array
$propertyUsers = propertyUsers::whereIn('property_id', $ids)->get(); // Get the propertyUsers model
foreach($properties as $property) {
$property->property_users = $propertyUsers->where('property_id', $property->id); // Not even sure you can do that, proerty_users may not been writable
}
Alright, after reading here and there.
I found this article: https://laravel-news.com/eloquent-eager-loading
The solution is really nice. Simply:
$properties = $this->run(GetProperties::class);
//lazy load
$properties->load('property_users');
$properties = $properties->map(function($property) {
$user = $property->property_users->first(function($user) {
if($user->user_id == Auth::user()->id) {
return $user;
}
})->only('chat_username');
return [
'name' => $property->name,
'id' => $property->id,
'chat_username' => $user['chat_username']
];
});
After checking query logs:
//auth query done by middleware
[2019-05-21 07:59:11] local.INFO: select * from `users` where `auth_token` = ? or `temporary_auth_token` = ? limit 1 ["token_removed_for_security_purpose","token_removed_for_security_purpose"]
//These are the queries made:
[2019-05-21 07:59:11] local.INFO: select `properties`.*, `property_users`.`user_id` as `pivot_user_id`, `property_users`.`property_id` as `pivot_property_id` from `properties` inner join `property_users` on `properties`.`id` = `property_users`.`property_id` where `property_users`.`user_id` = ? [8]
[2019-05-21 07:59:11] local.INFO: select * from `property_users` where `property_users`.`property_id` in (2, 4)
This way, I can keep my GetProperties as small as possible, then just lazy load it whereever I need it.

latest() helper function in laravel

I'm building a small application where I'm having many to many relationship between two models something like this:
class Contact extends Model
{
public function company()
{
return $this
->belongsToMany('App\Company', 'company_contact', 'company_id', 'contact_id')->withTimestamps();
}
}
Now while retrieving this I want only the latest model through the pivot table or you may say relational table, for this I'm trying to implement:
public function getData()
{
$allData = Contact::all();
foreach($allData as $data)
{
$getCompany = $data->company()->latest()->first();
$data->company = $getCompany;
}
return response()->json(['model' => $allData], 200);
}
But I'm unable to retrieve the latest table it is showing the same old value or the first value.
Guide me how can I achieve this.
You can try this :
latest() is a function defined in Illuminate\Database\Query\Builder Class.
public function latest($column = 'created_at')
{
return $this->orderBy($column, 'desc');
}
So, It will just orderBy with the column you provide in descending order with the default column will be created_at
OR
public function getData()
{
$allData = Contact::all();
foreach($allData as $data)
{
$getCompany = $data->company()->orderBy('created_at', 'desc')->first();
$data->company = $getCompany;
}
return response()->json(['model' => $allData], 200);
}
So basically idea is if you are finding the relational data from many to many relation like $data->company() in this question and try to sort this with latest it will sort the company table with created_at sorting desc in order to get relational latest data you need to sort through pivot tables i.e.
$getCompany = $data->company()->withPivot('created_at')->orderBy('pivot_cr‌​eated_at', 'desc')->first();
This is how I achieved the latest relational table.
Note: You must have pivot table in your relation, in this answer created_at is the pivot field I'm using.

Laravel ORM should return Relation

Is it possible to return an value for an hasOne relation directly with an Model?
For example:
$List = Element::orderBy('title')->get();
The Element has a "hasOne" Relation to an column:
public function type()
{
return $this->hasOne('App\Type', 'id', 'type_id');
}
How can i now return automatically the "type" for the Model?
At the Moment i am looping through all Elements, and build my own "Array" of Objects, including the Key of "type" in this example. But ill prefer to do this only in my Model.
Ill know how to add a "normal" property, but can it be someone from an relation?
public function getTypeAttribute($value)
{
return // how ?
}
protected $appends = array('type');
Is this possible?
Edit:
A workaround could be to use DB:: to return the correct value - but ill dont thing thats a good workaround: like:
public function getTypeAttribute($value)
{
// make a query with $this->type_id and return the value of the type_name
}
protected $appends = array('type');
you need to eager load your relations when getting the Element:
$list = Element::with('type')->orderBy('title')->get();
then access the type using
foreach ($list as $item) {
echo $item->type->type_name;
}
where type_name would be the name of a column in the types table
Make a query scope and in that scope, join your attributes from other tables.
http://laravel.com/docs/5.1/eloquent#query-scopes
http://laravel.com/docs/5.1/queries#joins

Laravel eloquent query (one to one)

I have two tables, tracks and echonestrecords. Tracks have one record in the echonestrecords table with information about the track.
tracks has these columns:
id, title, artist, created_at, updated_at
echonestrecords has these columns:
id, track_id, tempo, mode, key
The track object has the hasOne()-function to return the echonestrecord.
The echonestrecord object has the belongsTo()-function to return the track.
I want to make an Eloquent-query to get all tracks which have an empty row in the echonestrecords-table. That is, a row there the tempo is null.
I can do it like this with the raw query, which doesn't return Track objects.
$tracks = DB::table('tracks')
->select('tracks.*')
->join('echonestrecords','echonestrecords.track_id','=','tracks.id')
->where('echonestrecords.tempo',null,'NULL')
->get();
But I can't figure out how to do this with Eloquent. I figured it must be something like:
$tracks = (array('echonestrecord' => function($query) {
$query->where('tempo', null, 'NULL');
}))->get();
But this returns all tracks! I'm confused.
Can you help me?
I solved it. Yahooo..
The answer was using whereHas:
$tracks = Track::whereHas('EchoNestRecord', function($q)
{
$q->whereNull('tempo');
})->paginate(3);
What you probably need is to create a relation and filter it a little bit more:
class EchoNestRecord extends Eloquent {
protected $table = 'echonestrecords';
}
class Track extends Eloquent {
protected $table = 'tracks';
public function RecordsWithNoTempo()
{
return $this->hasOne('EchoNestRecord')->whereNull('tempo');
}
}
Then you should be able to:
$track = Track::find(1);
foreach($track->recordsWithNoTempo as $record)
{
echo $record->name;
}
Use
dd(DB::getQueryLog());
To check the generated query.

Resources