Laravel permission cache - laravel

I am using my own implementation for user roles and permissions in laravel how to cache all the permissions and roles on user login and also to refresh the cache when new record is added.
My tables are users ,roles, permissions,permission_role,permission_user,role_user. This is my table structure
i have created on of the provider and added this code to boot method
Permission::get()->map(function ($permission)
{
Gate::define($permission->name, function ($user) use ($permission)
{
return $user->hasPermission($permission);
});
});
it is working fine but it is running the query each time which gets slowing down my application is there any way to cache all the permissions
here is my PermissionServiceProvider class
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Blade;
use App\Models\Permission;
class PermissionServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
if (Schema::hasTable('permissions'))
{
Permission::get()->map(function ($permission)
{
Gate::define($permission->name, function ($user) use ($permission)
{
return $user->hasPermission($permission);
});
});
}
Blade::directive('role', function ($role)
{
return "<?php if(Auth::user()->hasRole({$role})): ?>";
});
Blade::directive('endrole', function ($role)
{
return "<?php endif; ?>";
});
}
/**
* Register services.
*
* #return void
*/
public function register()
{
//
}
}
and finally inside the user model
public function hasPermission($permission)
{
return $this->hasPermissionThroughRole($permission) || (bool) $this->permissions->where('name',$permission->name)->count();
}
public function hasPermissionThroughRole($permission)
{
foreach($permission->roles as $role)
{
if($this->roles->contains($role))
{
return true;
}
}
return false;
}
and also here is my bit bucket repo
https://manojkiran#bitbucket.org/manojkiran/userrolesandpermissions.git
i have tried the method of #emtiaz-zahid https://stackoverflow.com/a/53511803/8487424 its working fine to cache all the permisisons in permissions table but is there any way to cahe all the permisisons and also the permissions to specifc user and role of the currently logged in user

You could use Laravel model cache like shown in the following link:
https://laravel-news.com/laravel-model-caching
I would create a method for getting and caching the current users roles and permissions.
You can read more about Caching here:
https://laravel.com/docs/5.7/cache
I hope this helps
Update
Try something like this
if (Schema::hasTable('permissions') ) {
$userId = Auth::user()->id
return Cache::remember($this->cacheKey() . $userId . ':permissions', 60, function() use ($userId) {
return Permission::where('user_id', $userId)->get();
}
}
Then you just need to update your hasPermission() method to check the cache.
Also try to avoid map() as php takes a lot longer than sql

You can store your cache in service provider. I just sharing my method
$permissions = Cache::rememberForever('permissions', function () {
return Permission::get();
});
this will remember permissions with key name permissions forever until you change it
you can also remember this with specific time
$permissions = Cache::remember('permissions', 3600, function () {
return Permission::get();
});
I placed it in app service provider so that every time on app load it check if permissions found in cache then don't run the query and if not found then take it from DB
so your script could be like this after the cache part:
$permissions->map(function ($permission)
{
Gate::define($permission->name, function ($user) use ($permission)
{
return $user->hasPermission($permission);
});
});
find more on Laravel Documentation

finally I found the solution.
Artisan cache clear command is not working well.
Try to remove the cache on this folder.
storage/framework/cache/data
This should work for sure.
Good luck

Related

How to Prevent Other User Access based on Authentication and User ID in Laravel 7

I have an ongoing Laravel project and I'm currently learning. So there will be multiple users that can register for the system. For example, user1 created an account and made transactions and changes on his account. When user2 register and login, user2 sees everything in user1's account instead of a fresh blank dashboard to get started with. I tried adding middleware->('auth'); in my routes but it didn't change anything.
HomeController
<?php
namespace App\Http\Controllers;
use App\MoneyTrade;
use App\MoneyTradeDeposit;
use App\Withdrawal;
use Illuminate\Http\Request;
use Laravel\Ui\Presets\Vue;
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Show the application dashboard.
*
* #return \Illuminate\Contracts\Support\Renderable
*/
public function index()
{
$moneytrades = MoneyTrade::all();
$moneytradeDeposits = MoneyTradeDeposit::all();
$amountSum = MoneyTradeDeposit::sum('amount');
$balance = Withdrawal::sum('amount');
return view('dashboard', compact('moneytrades', 'moneytradeDeposits', 'amountSum', 'balance'));
}
public function dashboard()
{
$moneytrades = MoneyTrade::all();
$moneytradeDeposits = MoneyTradeDeposit::all();
$amountSum = MoneyTradeDeposit::sum('amount');
$withdrawal = Withdrawal::all();
$balance = Withdrawal::sum('amount');
return view('dashboard', compact('moneytrades', 'moneytradeDeposits', 'amountSum',
'withdrawal', 'balance'));
}
public function stocks()
{
$amountSum = MoneyTradeDeposit::sum('amount');
return view('home.stocks', compact('amountSum'));
}
public function support()
{
return view('home.support');
}
public function withdrawal()
{
$moneytrades = MoneyTrade::all();
$moneytradeDeposits = MoneyTradeDeposit::all();
$amountSum = MoneyTradeDeposit::sum('amount');
$withdrawal = Withdrawal::all();
$balance = Withdrawal::sum('amount');
return view('home.withdrawal', compact('moneytrades', 'moneytradeDeposits', 'amountSum',
'withdrawal', 'balance'));
}
}
web.php
Route::get('/', function () {
return view('welcome');
});
Route::get('/send-mail', 'SendMailController#send')->middleware('auth');
Auth::routes();
Route::get('register/agreement', 'Auth\RegisterController#show')->name('register.agreement');
Route::get('/home', 'HomeController#index')->name('home')->middleware('auth');;
Route::get('/dashboard', 'HomeController#dashboard')->name('home.dashboard')->middleware('auth');
Route::get('/my-account', 'MyAccountController#index')->name('myaccount.index')->middleware('auth');
Route::patch('/my-account/update', 'MyAccountController#update')->name('myaccount.update')->middleware('auth');
Route::get('/stocks', 'HomeController#stocks')->name('home.stocks')->middleware('auth');
Route::get('/support', 'HomeController#support')->name('home.support')->middleware('auth');
Route::get('/withdrawal-information', 'HomeController#withdrawal')->name('home.withdrawal')->middleware('auth');
Route::resource('withdrawal', 'WithdrawalController')->middleware('auth');
Route::resource('moneytrade', 'MoneyTradeController')->middleware('auth');
Route::resource('moneytrade-deposit', 'MoneyTradeDepositController');
Route::get('/account-removed', 'MoneyTradeController#destroy')->name('mt.delete')->middleware('auth');
Route::get('/trading-account', 'MoneyTradeController#view')->name('mt.view')->middleware('auth');
Route::get('/trading-account/deposits', 'MoneyTradeController#deposit')->name('mt.deposit')->middleware('auth');
How can I achieve this and prevent other users to access other dashboards that's not their own? I don't have roles and just normal users. I just want to prevent one user from accessing other user's dashboard. Thank you!

Laravel Policies registration return false on Voyager

I added Voyager to my project, which uses a policy to check if a user can view/edit a specific project.
When I try to open the Projects table from Voyager, I get a 403 error.
It seems that when I remove the policy from my "policies" array in AuthServiceProvider, I am able to access the projects table in Voyager just fine.
I tried adding in my policy a check on $user->role_id == 1 (check if user is admin), but still no success, even if I replace the response of the policy to true.
Any idea what the issue might be? Thanks
Edit:
Desired behavior: I want to restrict the user from editing/viewing projects that are not his, however I want the admin to be able to access all projects from Voyager.
I ended up fixing it by adding the policy that I am using on the Projects bread, and had to add browse, add, edit, delete and read methods returning true.
It seems that once you have a policy registered Voyager will pick it up so you need to tell it how it should behave.
<?php
namespace App\Policies;
use App\User;
use App\Project;
use Illuminate\Support\Facades\DB;
use Illuminate\Auth\Access\HandlesAuthorization;
class ProjectPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
}
public function browse(){
return true;
}
public function add(){
return true;
}
public function delete(){
return true;
}
public function edit(){
return true;
}
public function read(){
return true;
}
public function access(User $user, Project $project){
return $project->owner_id == $user->id ;
}
}
Have you tried some basics steps like :
php artisan route:clear
composer dump-autoload

Use Auth in AppServiceProvider

I need the ID of the user who is logged in to get a photo in the profile table, here I am trying to use View but only in the index function that gets $profile, I want all files in the view to have $profile
public function index(){
$profil = Profil_user::where('user_id',$auth)->first();
View::share('profil', $profil);
return view('user.index');
}
I have also tried AppServiceProvider but I get an error in the form of a null value if I don't log in, is there a solution to my problem?
public function boot(){
$auth = Auth::user();
dd($auth);
}
exist several way to pass a variable to all views. I explain some ways.
1. use middleware for all routes that you need to pass variable to those:
create middleware (I named it RootMiddleware)
php artisan make:middleware RootMiddleware
go to app/Http/Middleware/RootMiddleware.php and do following example code:
public function handle($request, Closure $next) {
if(auth()->check()) {
$authUser = auth()->user();
$profil = Profil_user::where('user_id',$authUser->id)->first();
view()->share([
'profil', $profil
]);
}
return $next($request);
}
then must register this middleware in app/Http/Kernel.php and put this line 'root' => RootMiddleware::class, to protected $routeMiddleware array.
then use this middleware of routes or routes group, for example:
Route::group(['middleware' => 'root'], function (){
// your routes that need to $profil, of course it can be used for all routers(because in handle function in RootMiddleware you set if
});
or set for single root:
Route::get('/profile', 'ProfileController#profile')->name('profile')->middleware('RootMiddleware');
2. other way that you pass variable to all views with view composer
go to app/Http and create Composers folder and inside it create ProfileComposer.php, inside ProfileComposer.php like this:
<?php
namespace App\Http\View\Composers;
use Illuminate\View\View;
class ProfileComposer
{
public function __construct()
{
}
public function compose(View $view)
{
$profil = Profil_user::where('user_id', auth()->id)->first();
$view->with([
'profil' => $profil
]);
}
}
now it's time create your service provider class, I named it ComposerServiceProvider
write this command in terminal : php artisan make:provider ComposerServiceProvider
after get Provider created successfully. message go to config/app.php and register your provider with put this \App\Providers\ComposerServiceProvider::class to providers array.
now go to app/Providers/ComposerServiceProvider.php and do like following:
namespace App\Providers;
use App\Http\View\Composers\ProfileComposer;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
View::composer(
'*' , ProfileComposer::class // is better in your case use write your views that want to send $profil variable to those
);
/* for certain some view */
//View::composer(
// ['profile', 'dashboard'] , ProfileComposer::class
//);
/* for single view */
//View::composer(
// 'app.user.profile' , ProfileComposer::class
//);
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
}
}
3. is possible that without create a service provider share your variable in AppServiceProvider, go to app/Provider/AppServiceProvider.php and do as follows:
// Using class based composers...
View::composer(
'profile', 'App\Http\View\Composers\ProfileComposer'
);
// Using Closure based composers...
View::composer('dashboard', function ($view) {
//
});
I hope be useful
you can use this
view()->composer('*', function($view)
{
if (Auth::check()) {
$view->with('currentUser', Auth::user());
}else {
$view->with('currentUser', null);
}
});

Laravel Global Variable to specific views

I would like assistance with calling a global variable on Laravel app for specific pages or routes.
This is my current code which works on login
App\Providers\AppServiceProvider
public function boot()
{
view()->composer(['auth.login'], function ($view) {
$view->with('settings', AppSetting::where('id',1)->first());
});
}
This is the route for the login page
Route::get('/', function () {
return view('auth.login');
});
[Edit 1]
On the login page , I used this code bellow to get the app version
{{$settings->app_version}}
After digging a little I think a good solution might be caching your AppSetting Model.
Write the given code in App\Providers\RouteServiceProvider
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
parent::boot();
App::before(function($request) {
App::singleton('settings', function(){
return AppSetting::where('id',1)->first();
});
// If you use this line of code then it'll be available in any view
// as $settings but you may also use app('settings') as well
View::share('settings', app('settings'));
});
}
}
App::singleton will call once AppSetting::where('id',1)->first() and after one call your settings will be cached.
And you can use {{$settings->app_version}} in your view.
Reference: stackoverflow.com/a/25190686/7047493

Prevent using same query twice, once in each view composer

I have two View composers in my AppServiceProvider class, below:
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
View::composer('left', function ($view)
{
if (Auth::check())
{
$user = Auth::user();
// Gets a list of the people the user is following
$usersFollowing = Following::where('user_id', $user->id)
->get();
// More queries here
View::share('usersFollowing', $usersFollowing);
}
});
View::composer('right', function ($view)
{
if (Auth::check())
{
$user = Auth::user();
// Gets a list of the people the user is following
$usersFollowing = Following::where('user_id', $user->id)
->get();
// More queries here
View::share('usersFollowing', $usersFollowing);
}
});
}
}
As you can see, both composers request the same query data ($usersFollowing). Both of these layouts (left.blade.php and right.blade.php) are called on all of my pages (by including them in the base layout).
The problem with this is that the page is requesting $usersFollowing twice on a single page load. It's calling the query once for left.blade.php and once for right.blade.php.
I'm also calling Auth::user() twice, once in each composer.
How can I prevent these queries from being called twice for the same request, and only call it once?
I think it is simple to move your queries to top of your method and use them inside both View composers. This way your query would only run once.
Here is my proposed way of doing this;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
$user = Auth::user();
// Gets a list of the people the user is following
$usersFollowing = Following::where('user_id', $user->id)
->get();
// You can use `use` keyword to access external variables inside callback function.
//Both of these variables will be accessible inside callback
View::composer('left', function ($view) use ($usersFollowing,$user)
{
if (Auth::check())
{
// More queries here
View::share('usersFollowing', $usersFollowing);
}
});
View::composer('right', function ($view) use ($usersFollowing,$user)
{
if (Auth::check())
{
// More queries here
View::share('usersFollowing', $usersFollowing);
}
});
}
}
I hope this can be helpful and you can generalize this method to any other situations where you need this kind of functionality.

Resources