using cache with deferent data depending on parameter with laravel - laravel

I have data displaied depend on the username written in the URL, and I wanted to cache this data, I have used this way:
function info($uname) {
$info = Cache::remember('get-all-info'.$uname, env('Cache_DURATION'), function() use($uname){
$user = User::where('name', '=', $uname)->firstOrFail();
return $user !=null ? $user->about : null;
});
return view('info', compact(['info']));
}
and it is working perfectly.... if I change the username ($uname) the data will return for this specific user...
my question is how does it work? why does data come for a user, not from the cache for a previous user?
is there more than one cache here depending on the user name? or what?
and is this way is good in a matter of performance and speed for either API or normal web application

Related

How to check if user password is null or not in the database and passing that information to Vue app?

The objective here is not to know the actual password of an user, but to know if the user has a password set or not in the database. I can distinguish the users who have passwords set, i.e. password != null, versus users who don't have passwords set, i.e. password == null, inside my Laravel controller, but when I try to pass the user list to my Vue app using an axios.get request, all passwords come up as 'undefined' even though there are users with hashed passwords in the database. The following is a sample of my Vue code:
axios.get('/admin/user')
.then(function (response) {
self.users = response.data;
for(var i=0; i<self.users.length; i++) {
console.log(self.users[i].password) // outputs 'undefined' for all user passwords instead of the hashed database passwords
}
})
.catch(function (error) {
// handle error
console.log(error);
});
and /admin/user from above is defined in my routes directory in web.php as:
Route::get('/admin/user', 'UserController#get');
and for the purpose of this example, my UserController#get function is defined as:
public function get()
{
$user = User::get();
return response($user, 200);
}
Any advice on how to distinguish users who have passwords set and not set would be appreciated.
As #matiaslauriti mentioned in the comment, User model deletes password field during serialization. As such you should avoid sending out passwords from your server even if they're hashed. In your particular usecase there is no need to send passwords out. You can simply check if the password is set in the backend and attach this info to the response object.
public function get()
{
$user = User::get();
if (empty($user->password)) {
$user->isPasswordSet = false;
} else {
$user->isPasswordSet = true;
return response($user, 200);
}

Laravel Auth::User() performance

I have a question that whenever we call Auth::User() then its execute the query to fetch record or it have a saved instance?
Example 1
echo Auth::User()->name;
echo Auth::User()->email;
echo Auth::User()->phone;
Example 2
$userInfo=Auth::User();
echo $userInfo->name;
echo $userInfo->email;
echo $userInfo->phone;
Which one should be used performance wise?
Answer and example
Call to the database will be made only the first time you call Auth::user(), after that Laravel will store the user data and each call after that will get the stored instance rather then query the database again.
You can take a look at the vendor\laravel\framework\src\Illuminate\Auth\SessionGuard.php file under user() method. This is the code I copied from my current project which uses Laravel 7.x and this is the function called by Auth::user().
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if ($this->loggedOut) {
return;
}
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
$this->user = $this->userFromRecaller($recaller);
if ($this->user) {
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($this->user, true);
}
}
return $this->user;
}
Debugbar options
Also as the comment before me pointed out, it is good to download Debugbar for Laravel https://github.com/barryvdh/laravel-debugbar. It will enable you to take a look into queries being executed, views being rendered, requests being sent, and much more.
Other option is Laravel's native solution https://laravel.com/docs/8.x/telescope. I have never personally used it and IMO the first one is simpler to use.
Notes on good practice
Although both examples will essentially do the same thing, I think it is much better to use the second example. Not because of the performance, but rather to make your code readable in future. If you define the user only one time and assign Auth::user() result to it, in future it will be more obvious what it is, and plus, you can easily change what are you assigning to the $userInfo variable (maybe you want to get user from another guard in future, etc.) without having to change it on N places throughout the code.

How to utilize Laravel Cache in API?

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();

Caching Eloquent models in Laravel 5.1

I've created an API using Laravel and I'm trying to find out how to cache Eloquent models. Lets take this example as one of the API endpoints /posts to get all the posts. Also within the method there are various filter options such as category and search and also gives the option to expand the user.
public function index()
{
$posts = Post::active()->ordered();
if (Input::get('category')) $posts = $posts->category(Input::get('category'));
if (Input::get('search')) $posts = $posts->search(Input::get('search'));
if ($this->isExpand('user')) $posts = $posts->with('user');
$posts = $posts->paginate($this->limit);
return $this->respondWithCollection($this->postTransformer->transformCollection($posts->all()), $posts);
}
I have been reading up and found in Laravel 4 you could cache a model like this
return Post::remember($minutes);
But I see this has been removed for Laravel 5.1 and now you have to cache using the Cache facade, but is only retrievable by a single key string.
$posts = Cache::remember('posts', $minutes, function()
{
return Post::paginate($this->limit);
});
As you can see, my controller method contains different options, so for the cache to be effective I would have to create a unique key for each option like posts_cagetory_5, posts_search_search_term, posts_category_5_search_search_term_page_5 and this will clearly get ridiculous.
So either I'm not coming across the right way to do this or the Laravel cache appears to have gone backwards. What's the best solution for caching this API call?
As the search is arbitrary, using a key based on the search options appears to be the only option here. I certainly don't see it as "ridiculous" to add a cache to for expensive DB search queries. I may be wrong as I came by this post looking for a solution to your exact problem. My code:
$itemId = 1;
$platform = Input::get('platform'); // (android|ios|web)
$cacheKey = 'item:' . $itemId . ':' . $platform;
$item = Item::find(1);
if( Cache::has($cacheKey) ) {
$result = Cache::get($cacheKey);
} else {
$result = $this->response->collection( $item, new ItemTransformer( $platform ) );
Cache::tags('items')->put($cacheKey, $result, 60); // Or whatever time or caching and tagged to be able to clear the lot in one go...
}
return $result;
I realise that my example has less complexity but it seems to cover all the bases for me. I then use an observer to clear the cache on update.

How to "Refresh" the User object in Laravel?

In Laravel you can do this:
$user = Auth::user();
Problem is, if I do changes on items on that object, it will give me what was there before my changes. How do I refresh the object to get the latest values? I.e. To force it to get the latest values from the DB?
You can update the cache object like this.
Auth::setUser($user);
for Example
$user = User::find(Auth::user()->id);
$user->name = 'New Name';
$user->save();
Auth::setUser($user);
log::error(Auth::user()->name)); // Will be 'NEW Name'
[This answer is more appropriate for newer versions of Laravel (namely Laravel 5)]
On the first call of Auth::user(), it will fetch the results from the database and store it in a variable.
But on subsequent calls it will fetch the results from the variable.
This is seen from the following code in the framemwork:
public function user()
{
...
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
...
}
Now if we make changes on the model, the changes will automatically be reflected on the object. It will NOT contain the old values. Therefore there is usually no need to re-fetch the data from the database.
However, there are certain rare circumstances where re-fetching the data from the database would be useful (e.g. making sure the database applies it's default values, or if changes have been made to the model by another request). To do this run the fresh() method like so:
Auth::user()->fresh()
Laravel does do that for you, HOWEVER, you will not see that update reflected in Auth::user() during that same request. From /Illuminate/Auth/Guard.php (located just above the code that Antonio mentions in his answer):
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method because that would tremendously slow an app.
if ( ! is_null($this->user))
{
return $this->user;
}
So if you were trying to change the users name from 'Old Name' to 'New Name':
$user = User::find(Auth::user()->id);
$user->name = 'New Name';
$user->save();
And later in the same request you try getting the name by checking Auth::user()->name, its going to give you 'Old Name'
log::error(Auth::user()->name)); // Will be 'Old Name'
A little late to the party, but this worked for me:
Auth::user()->update(array('name' => 'NewName'));
Laravel already does that for you. Every time you do Auth::user(), Laravel does
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveByID($id);
}
It nulls the current user and if it is logged, retrieve it again using the logged id stored in the session.
If it's not working as it should, you have something else in your code, which we are not seeing here, caching that user for you.

Resources