I'm trying to cache all records of the query for 60 minutes by the following method (Method 1)
Route::get('categoryList', function() {
return app\CategoryDetails::remember(60)->get();
});
I followed this tutorial link (Tip 5: Cache Database Queries)
But I'm getting this error:
Call to undefined method Illuminate\Database\Query\Builder::remember()
I don't know what I'm missing here.
BTW, I know I can cache entire records by the following method (Method 2):
Route::get('categoryList', function() {
$category = Cache::remember('category', 10, function() {
return \App\CategoryDetails::all();
});
return $category;
});
and this is working perfectly.
I am just curious why the first method is not working for me.
Laravel 5 removed this functionality. You now have to store the cache yourself:
Route::get('categoryList', function () {
return Cache::remember('category-details', 60, function () {
return App\CategoryDetails::all();
});
});
From the upgrade docs:
Eloquent no longer provides the remember method for caching queries. You now are responsible for caching your queries manually using the Cache::remember function.
Consider using a laravel eloquent query caching library called rememberable
It does a pretty good job.
Related
In my company we have a three user roles: admin, physician and client. All of them can view one of the records table where we have about 1 million rows and we are in need of caching the results from database.
I've read 10's of posts on Stack and else but I am still trying to figure out the proper way of how to caching.
What I've read is that the proper way is to cache per page, so I cache page 1, page 2 etc based on user page selection. This all works fine.
BUT each user role sees different datasets with different filters selected by them and this is where the problem starts. I cache the results and then filtering the paginated 10 rows seems kind of redundant.
I don't know if I should cache results for each user role with the selected parameters?
Or should I cache all the results first, then load the needed relationships and filter the collection with the parameters from user and then create pagination?
Or shouldn't I be using cache at all in this example and just use simple pagination?
// Set the cache time
$time_in_minutes = 5 * 60;
// Request page and if not set then default page is 1
$page = $paginationObject['page'];
// Set items per page
$per_page = $paginationObject['perpage'] ? $paginationObject['perpage'] : 10;
// Set the cache key based on country
$cache_key = "l04ax_pct_dispensing_forms_{$request->get('country')}_page_{$page}_per_page_$per_page";
// Cache::forget($cache_key);
// Set base query for results
$baseQuery = $this->model->with(['details', 'patient']);
// Assign appropriate relations based on user role
if (Auth::user()->isPhysician()) {
$baseQuery->physicianData();
}
else if (Auth::user()->isManufacturer()) {
$baseQuery->manufacturerData();
}
else if (Auth::user()->isSuperAdmin() || Auth::user()->isAdmin()) {
$baseQuery->adminData();
}
//--------------------------------------
// Add filtering params from request
// -------------------------------------
$baseQuery->when($request->has('atc_code'), function ($query) use ($request) {
if ($request->get('atc_code') === NULL) {
throw new RequestParameterEmpty('atc_code');
}
$query->whereHas('details', function ($subQuery) use ($request) {
$subQuery->where('atc_code', $request['atc_code']);
});
})
->when($request->has('id'), function ($query) use ($request) {
if ($request->get('id') === NULL) {
throw new RequestParameterEmpty('id');
}
$query->where('l04ax_dispensing_forms.id', $request['id']);
})
->when($request->has('pct_patients_hematology_id'), function ($query) use ($request) {
if ($request->get('patient_id') === NULL) {
throw new RequestParameterEmpty('patient_id');
}
$query->where('patient_id', $request['patient_id']);
})
->when($request->has('physician_id'), function ($query) use ($request) {
if ($request->get('physician_id') === NULL) {
throw new RequestParameterEmpty('physician_id');
}
$query->where('physician_id', $request['physician_id']);
})
->when($request->has('date'), function ($query) use ($request) {
if ($request->get('date') === NULL) {
throw new RequestParameterEmpty('date');
}
$query->whereDate('created_at', Carbon::parse($request->get('date'))->toDateString());
})
->when($request->has('deleted'), function ($query) use ($request) {
if ($request->get('only_deleted') === NULL) {
throw new RequestParameterEmpty('only_deleted');
}
$query->onlyTrashed();
})
->when($request->has('withTrashed'), function ($query) use ($request) {
if ($request->get('withTrashed') === NULL) {
throw new RequestParameterEmpty('withTrashed');
}
$query->withTrashed();
});
// Remember results per page into cache
return Cache::remember($cache_key, $time_in_minutes, function () use ($baseQuery, $per_page, $page) {
return new L0axPctDispensingFormsCollection($baseQuery->paginate($per_page, ['*'], 'page', $page));
});
In this example the results are cached per page, but when different user logs in, then the results are wrong.
What would be the best way to approach this?
I wouldn't recommend caching this because of the problem you have already encountered. Caching is massively helpful in some areas (e.g. for reference data like a persistent list of countries or currencies), but for user-specific data I would avoid.
If you really did want to cache you could use cache tagging (supported by redis using the phpredis driver only) to tag by user id. However, as mentioned, I wouldn't recommend in this scenario!
If your desire to cache is driven by the scenario where your pages are loading slowly I would recommend installing Laravel Debugbar, and checking to see how many queries your api calls are generating.
If you find a single api call is generating more queries than the number of records you are loading, then you likely are having the 'n + 1 problem' and need to eager load any nested relationships rather than call them in your resource.
P.s You can immediately reduce the number of queries generated by this controller method by only calling Auth::user() once. e.g. $user = Auth::user() and then $user->isSuperAdmin();
Error is:
Serialization of 'Closure' is not allowed
Error at:
.../vendor/laravel/framework/src/Illuminate/Cache/RedisStore.php:295
Throws when remembering forever on Cache for the first time.
After second try (when reloading browser) it works as it should work.
public function cache()
{
$task = $this;
return Cache::rememberForever('apply:' . $task->apply->slug . ':' . $task->slug, function () use ($task) {
return $task;
});
}
Interesting part is this. So it works on caching $apply on Apply's index page. (The code is the same)
Note: This issue is related to Redis directly. Please don't mention old questions about serialization. You can check official Laravel 6.x documentation too. Everything is added related to it: https://laravel.com/docs/6.x/cache#retrieving-items-from-the-cache
I fixed it by manually storing and returning data if it exists on cache (how rememberForever() should be work).
public function cache () {
$slug = 'task:'.$this->slug;
if(Cache::has($slug)) return Cache::get($slug);
if(!Cache::put($slug, $this)) throw new ProtocolException(1045);
return Cache::get($slug);
}
I have made a ServiceProvider to load data on several views. Like this:
View::composer(['components.navigation.main.search','search.*','page-parts.cats','page-parts.categories_menu','page-parts.categories_more','page-parts.cats_top','components.modals.off-category'],function ($view) {
$view->with([
'toplevel_categories' => Category::topLevel()->orderBy('name')->get(),
]);
});
But on several html pages he needs to load multiple of these views and I don't want to load the topLevel categories each time to avoid overload and less runtime.
Can I store the loaded data (toplevel_categories) in a session or what is the most efficient way to handle this problem?
You could simply cache the variable and use it in the callback like:
$topLevelCategories = Category::topLevel()->orderBy('name')->get();
View::composer([], function($view) use ($topLevelCategories) {
$view->with([
'toplevel_categories' => $topLevelCategories
}
You could even use the cache mechanic from laravel itself to save an additional query, like caching it for 30 minutes (assuming the database hasnt changed in the meantime):
// Save the categories in the cache or retrieve them from it.
$topLevelCategories = Cache::remember('topLevelCategories', 30, function() {
return Category::topLevel()->orderBy('name')->get();
});
Note that for Laravel 5.8 the second parameter is in SECONDS, for 5.7 and below it is in MINUTES.
Since your service provider is only loaded once per request/lifecycle this should do the trick.
How will it affect the performance? if I loaded a variable inside all the views using view composers even if I'm not using this variable inside all the views.
is it recommended to do that? please provide your answer with an article.
using this service provider.
View::composer(
[
'*',
],
function ($view) {
$masterLayout = ;//get variable from database
}
);
Well, obviously there will be a negative impact on the performance of the site if you're querying for data that is possibly not used, but, you could always use caching to alleviate the effect:
View::composer(['*'], function ($view) {
$masterLayout = Cache::rememberForever('master_layout', function() {
return DB::table('layouts')->where('name', 'master')->first();
});
});
I'm needing to chunk my query as it's making PHP run out of memory, however the below code just dumps null:
$chunked = $query->chunk(25, function ($events) {
//dd($events);
});
dd($chunked);
However, when I do the below, it dumps the first chunk of 25:
$chunked = $query->chunk(25, function ($events) {
dd($events);
});
//dd($chunked);
No combination of changing dd($events); to the likes of return $events, return true, iterating over each item in each chunk and returning that - works.
Am I stupid/doing something wrong or is this not working like it should?
chunk() is a helper method which you can use on an instance of Query Builder or Eloquent Builder. In both cases, the method returns void:
Query Builder
Eloquent Builder
This means that your $chunked variable will be always empty.
The syntax you need to employ is the following:
Query Builder
DB::table('users')->chunk(100, function($users) {
foreach ($users as $user) {
//
}
});
Eloquent
Flight::chunk(200, function ($flights) {
foreach ($flights as $flight) {
//
}
});
Basically, all you need to do is to use a loop within the callback function of chunk() to modify your results.