-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Idea] Use cache on models(Optional trait) #1833
Conversation
17eb668
to
b436c0e
Compare
This could be really good 👀 |
I'm going to pass on this for now. |
@freekmurze can you make model caching a top priority? Currently it's sending 4 SQL requests with each page load, which I think it not really efficient. |
@erikn69 what happend to this PR? |
@Restingo was closed, you can implement it on your own app if you need it |
@erikn69 thanks for your fast reply! Is it right that the current state of the package doesn't cache the relations (like this PR would do) so this is also the reason for performance issues with a large amount of roles and/or permissions? Do I need to fork the package completely or can I only change the role and permission classes to implement it myself? |
You can change your models and add the trait on |
So I don't need to implement the changes in the two traits and |
depends on your needs, works for me without those changes, with a workaround
laravel-permission/src/PermissionRegistrar.php Lines 270 to 273 in 6a3ed62
|
Would you be so kind to paste the workaround? Would be very helpful. Thank you! |
Try this if you are not using teams use Illuminate\Database\Eloquent\Collection;
use Spatie\Permission\PermissionRegistrar;
trait HasPermissionsCache {
protected function getPermissionCacheKey(string $relation): string {
return sprintf(PermissionRegistrar::$cacheKey.'.'.str_replace('\\','.',$this->getMorphClass()).'.%d.'.$relation, $this->getKey());
}
private function getCachedPermissions(string $relation){
if ($this->relationLoaded($relation)) {
return $this->getRelationValue($relation);
}
$cache = app(PermissionRegistrar::class)->getCacheRepository();
$cacheIsTaggable = $cache->getStore() instanceof \Illuminate\Cache\TaggableStore;
$class = $relation === 'roles' ? $this->getRoleClass() : $this->getPermissionClass();
$collection = $class::hydrate(
collect(($cacheIsTaggable?$cache->tags(['permissions']):$cache)->remember($this->getPermissionCacheKey($relation), config('session.lifetime', 120)* 60, function () use($relation) {
return $this->getRelationValue($relation)->map(function ($data) {
return ['i' => $data->id, 'n' => $data->name, 'g' => $data->guard_name];
})->all();
}))
->map(function ($item) { return ['id' => $item['i'], 'name' => $item['n'], 'guard_name' => $item['g']]; })->all()
);
$this->setRelation($relation, $collection);
return $collection;
}
public function getRolesAttribute(): Collection {
return $this->getCachedPermissions('roles');
}
public function getPermissionsAttribute(): Collection {
return $this->getCachedPermissions('permissions');
}
public function forgetModelAssignedPermissions() {
$cache = app(PermissionRegistrar::class)->getCacheRepository();
$cache->forget($this->getPermissionCacheKey('roles'));
$cache->forget($this->getPermissionCacheKey('permissions'));
}
public static function forgetAllModelsAssignedPermissions(){
$cache = app(PermissionRegistrar::class)->getCacheRepository();
$cacheIsTaggable = $cache->getStore() instanceof \Illuminate\Cache\TaggableStore;
if($cacheIsTaggable){
$cache->tags(['permissions'])->flush();
}else{
static::select((new static)->getKeyName())->get()->each(function ($model) use ($cache) {
$cache->forget($model->getPermissionCacheKey('roles'));
$cache->forget($model->getPermissionCacheKey('permissions'));
});
}
}
} |
Thanks I'll try! |
Or better on User model public function load($relations){
$relations = is_string($relations) ? func_get_args() : $relations;
foreach ($relations as $i=>$relation) {
$method = 'forgetModelCached' . ucfirst((string) $relation);
if (in_array($relation, ['roles', 'permissions']) && method_exists($this, $method)) {
$this->$method();
$this->loadCachedRelation($relation);
unset($relations[$i]);
}
}
return empty($relations) ? $this : parent::load($relations);
}
public function loadMissing($relations){
$relations = is_string($relations) ? func_get_args() : $relations;
foreach ($relations as $i=>$relation) {
$method = 'forgetModelCached' . ucfirst((string) $relation);
if (in_array($relation, ['roles', 'permissions']) && method_exists($this, $method)) {
if (!$this->relationLoaded($relation)) {
$this->loadCachedRelation($relation);
}
unset($relations[$i]);
}
}
return empty($relations) ? $this : parent::loadMissing($relations);
} And the same |
So even with this tweak I've got a loading time with 72 roles assigned of 1.06 s and with only one role it's around 200 ms. Is there another way to improve? |
But I can see that the cache is working with Redis as expected! |
$cache = app(PermissionRegistrar::class)->getCacheRepository(); | ||
$cacheIsTaggable = $cache->getStore() instanceof \Illuminate\Cache\TaggableStore; | ||
if ($cacheIsTaggable) { | ||
$cache->tags(['permissions'.str_replace('\\', '.', $this->getMorphClass())])->flush(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the use of $this
in static
context wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe needs work
👍 This PR is closed, fell free to make all the fixes you need on your implementation
This would be really nice on many projects. |
Maybe needs work
According #1760 with https://stackoverflow.com/a/56263511 info
Optional trait for cache models relations, using
config('session.lifetime')
as expiration time