Laravel 6.x - Cache 'pagination()' - laravel

I'm using Laravel pagination (https://laravel.com/docs/6.x/pagination#paginating-eloquent-results)
like this:
// the Controller contains
$users = App\User::paginate(15);
Running this code, I noticed that are executed two queries:
To get the number of total items: select count(*) as aggregate from user
To get the first 15 items: select * from user limit 15 offset 0
I'd like to cache this queries, and the question is: from App\User::paginate(15) is there a way to get the sql(s) that will be executed? select count(*) as aggregate from user and select * from user limit 15 offset 0?
The code App\User::paginate(15) return a LengthAwarePaginator class; can I get the sql executed from this class?
The idea is to create my own method to cache this pagination request. Something like:
// the Model contains
public static function paginateWithQueryCache($itemPerPage) {
$query = self::query();
$cacheKeyString = $query->toSql();
$cacheKeyStringMD5 = md5($cacheKeyString);
return \Cache::remember($cacheKeyStringMD5, 60, function() use ($itemPerPage) {
return self::paginate($itemPerPage);
});
}
// the Controller will be update with
$users = App\User:: paginateWithQueryCache(15);
The problem here is that the $query->toSql() used as a cache key, is a simple query to the model (select * from user) instead of a pagination query (select * from user limit 15 offset 0); this is a problem switching to second page, page=2.
Thank you.

The query select count(*) as aggregate from user (that is very slow in my case with millions of records in a database table) is executed from Illuminate\Database\Query::getCountForPagination(), then to cache it I need to extend the class and implement the cache.
I changed the approach and I decided to cache all pagination output:
// Controller
public function paginateCache($model, $cacheExpireInSeconds = 120)
{
$cacheKeyString = request()->fullUrl();
$cacheKeyStringMD5 = md5($cacheKeyString);
/* Closure to get data */
$func_get_data = function() use ($model) {
return $model::paginate(1000);
};
/* Caching */
return \Cache::remember($cacheKeyStringMD5, $cacheExpireInSeconds, $func_get_data);
}
public function index()
{
return $this->paginateCache(User::class);
}

Related

Laravel GroupBy with Sum added to Object

I searched all other questions before. I have to simple groupBy select and get sum out of column. But how to make 1 query out of this ( without merge ). Possible?
$Todo = Todo::selectRaw('sum(estimated_time) as amount')->groupBy('user_name')->get();
$Todo = Todo::get()->groupBy('user_name');
I would suggest you avoid using any raw SQL statements in Laravel.
If your goal is to get the sum of the estimated duration of all todos for each user, you can use eager loading.
For example you could first query all your users and eager load the todos.
$users = User::query()->with('todos')->get();
And then you can retrieve the sum of the estimated duration for all todos.
foreach($users as $user) {
$user->totalEstimatedTodoTime = $user->todos->sum('estimated_time')
}
If you use the total estimated todo time of a user often. You could even define an accessor
For example in your user model:
public function getTotalEstimatedTodoTimeAttribute() {
return $this->todos->sum('estimated_time');
}
Then you can retrieve the value like this:
$user->totalEstimatedTodoTime
Write this code in Model :
public function setXXXAttribute($value)
{
$this->XXX= Model::where('user_name' , $this->user_name)->sum('estimated_time');
}
public function getXXXAttribute($value)
{
return $this->XXX
}

Laravel left join check if a conditon is greater than a count

I would like to check a limit in number of user payment where a limit is set on user table.
So i have the following database structure
table user
id,name,...,pay_limit
and payment table
table payment
id, user_id, payment_ref
So i have created the following code
$query = User::query();
//other stuff
$query->leftJoin('payment','payment.user_id','=','user.id')
//stuck
Am stuck on how to check if the totals of payments on a user is not greater than the user pay_limit
How can i check the above in a query
Simple with relations. Suppose payment model is Payment and payment amount in payment_amount column
class User extends Model{
public function payments()
{
return $this->hasMany(Payment::class);
}
public function getIsOverLimitedAttribute(): bool
{
//if you check amount
return $this->payments()->sum('payment_amount') > $this->pay_limit;
//or if you check count of payments
return $this->payments()->count() > $this->pay_limit;
}
public function scopeNoOverLimited($query){
return $query->withCount('payments')->having('payments_count', '<', $this->pay_limit);
}
}
And use
if($user->isOverLimited){
//do stuff
}
Or get not over limited users:
User::noOverLimited()->get();
In terms of performance (since you want to be able to return all such users), the best approach is to store a summary column in your users table... total_payment
You can update all current users maybe via a migration file as such:
$table->integer('total_payment')->unsigned()->nullable();
DB::update("update users set total_payment = ('select count(id) from payments where user_id = id')");
Then to get all such users, you can do:
User::whereRaw('total_payment > pay_limit')->get();
Then add a PaymentObserver to increase the total_payment on new successful payments.
If you don't have access to modify the table, you can still use a scope like, but this can be performance intensive if being run all the time such as on user login without caching:
public function scopeAboveLimit($query)
{
$id = $this->id;//user id
return $query->whereRaw("pay_limit < ('select count(id) from payments where user_id = $id')");
}

Optimising Eloquent Relational Queries

We are looking at optimising our Laravel Eloquent queries on our search page, we have got the number of queries executed down to a good number.
However, we do see a consistent query that is running based on a relationship. That query is :
select * from `property_types` where `property_types`.`id` = 7 limit 1
This query is running if there's a unique property type per property.
I've taken a look at has and with queries in Laravel.
The relationship is set :
public function type()
{
return $this->belongsTo('App\Models\PropertyType', 'property_type_id', 'id');
}
We create a Property Title and URL using the relationship, so for a URL we use :
public function getUrlAttribute($pdf = false)
{
$property_route = ($pdf) ? 'property-pdf' : 'property';
// Format: /property/[NUMBER OF BEDS]-bed-[PROPERTY TYPE]-[FOR-SALE / TO-RENT]-in-[CITY]/[PROPERTY ID]
$items = [];
if ($this->beds) $items[] = $this->beds.' bed';
$items[] = $this->type->name ?? 'property';
}
So everytime a matching property is found, It's going back over that relationship and we're seeing the property_types query running up to 15 times per page.
Is there any suggestions on optimising this?

Laravel get result from query in side query by Eloquent in one object

I have two tables:
main_presentations
so here i have "id" and "isEnabled";
child_presentations
And here i have "id" , "isEnabled" and "idParent";
I want to select in one object this is my code:
public function MainSlider(MainPresentation $MainPresentations, ChildPresentation $ChildPresentations)
{
$MainPresentations = MainPresentation::where('isEnabled', true)->get();
foreach ($MainPresentations as $MainPresentation) {
$AnArray[] = ChildPresentation::where([
['idParent', $MainPresentation['id']],
['isEnabled', true]
])->get();
}
return $AnArray;
}
but this is the result:
enter image description here
What you are doing is executing a query per result, which can be ineffective when it starts getting bigger.
You can:
Use querybuilder
As it follows, you just build a query starting on ChildPresentation, set a relation to MainPresentation table by id and get the collection
public function MainSlider()
{
$childPresentations = ChildPresentation::join('main_presentations','main_presentations.id','child_presentations.idParent')
->where('child_presentations.isEnabled', true)->where('main_presentations.isEnabled', true)->get();
return $childPresentations;
}
If you want all the MainPresentations with their respective ChildPresentations, only the enables ones.
You can take advantage of Laravel relationships and eager loading.
https://laravel.com/docs/5.6/eloquent-relationships
First, set the relationships in your MainPresentation model
In MainPresentation.php
public function childPresentation {
return $this->hasMany('App\ChildPresentation', 'idParent', 'id');
}
Your MainSlider function would be:
(Btw, no idea why you're receiving two arguments if you're overriding them but doesn't matter)
public function MainSlider() {
$mainPresentations = MainPresentation::with(['childPresentations' => function ($advancedWith) {
child_presentation.isEnabled is true
$advancedWith->where('isEnabled', true);
}])
->where('isEnabled', true)->get()->toArray();
return $mainPresentations;
}
This will return an array of MainPresentations that contain an array of child_presentations, with all their childs.
This translates to two queries:
Select * from main_presentations where isEnabled = true;
Select * from child_presentations where isEnabled= true and id in (in the first query);
Laravel then does background work to create the structure you desire when you write ->toArray()
Note: If you have a $visible array in your MainPresentation model, be sure to add: 'childPresentation' to it, otherwise the toArray will not agregage the childs to the parent.
Second note: I advise following some standards whenever you're writing code, usually functions are named camelCase and variables are camelCase.

Laravel: Eager loading duplication

I'm trying to set up eager loading to prevent N+1 queries, but I'm finding that I have to duplicate some eager loads in order to get laravel to use the 'eager loaded' data. Duplicating the eager loads of course results in duplicated queries.
This is my load call:
$provider->load('ads.gallery.images', 'ads.thumbnail', 'ads.user', 'ads.provider.values.field', 'values.field', 'galleries.images');
A provider is a polymorphic subtype of a user. This is for a user profile page, which displays the user values list, as well as a list of all their ads. The ad display is a sub-view that's reused across the site and has a tab that displays the user values list as well. Yes it's redundant on this page, but that's how the site is designed.
I would expect laravel to reuse the already-loaded values.field list when the sub-view accesses it, but it doesn't, so I had to add ads.provider.values.field to the list, which duplicated the queries:
select `provider_field_values`.*, `provider_values`.`user_id` as `pivot_user_id`, `provider_values`.`value_id` as `pivot_value_id` from `provider_field_values` inner join `provider_values` on `provider_field_values`.`id` = `provider_values`.`value_id` where `provider_values`.`user_id` in ('3')
select * from `provider_fields` where `provider_fields`.`id` in ('1', '2', '3')
select `provider_field_values`.*, `provider_values`.`user_id` as `pivot_user_id`, `provider_values`.`value_id` as `pivot_value_id` from `provider_field_values` inner join `provider_values` on `provider_field_values`.`id` = `provider_values`.`value_id` where `provider_values`.`user_id` in ('3')
select * from `provider_fields` where `provider_fields`.`id` in ('1', '2', '3')
Is there any way I can eliminate this duplication? I'm not sure if the issue is in how the views access the data or if it's in the controller. I can add snippets of the view code if needed.
Edit: Model relationships:
//Provider model
public function values()
{
return $this->belongsToMany('ProviderFieldValue', 'provider_values', 'user_id', 'value_id');
}
//ProviderField model
public function fieldValues()
{
return $this->hasMany('ProviderFieldValue', 'field_id');
}
//ProviderFieldvalue model
public function field()
{
return $this->belongsTo('ProviderField');
}
//ProviderValue model (this is a pivot table)
public function provider()
{
return $this->belongsTo('Provider');
}
public function fieldValue()
{
return $this->belongsTo('ProviderFieldValue');
}

Resources