Laravel duplicate queries on user - php

The debugbar reads
368 statements were executed, 360 of which were duplicated, 8 unique
It turns out that with every check on my user, via auth()->user()->isCustomer() and other similar functions, it's actually looking up the user every time.
i.e.
public function hasRole($role)
{
// If the user has the 'admin' role, always authorize
if ($this->roles()->where('name', 'customer')->first() !== null) {
return true;
}
return null !== $this->roles()->where('name', $role)->first();
}
// Check for admin
public function isCustomer()
{
return $this->hasRole('customer');
}
How do I safely cache this information on the user object so the database isn't being hounded every time?
I also use the 404labfr/laravel-impersonate function on this project (so when you are an Admin, you can impersonate other users).

I think you can decrease queries by half using:
public function hasRole($role)
{
return null !== $this->roles()->whereIn('name', ['customer', $role])->first();
}

Related

Laravel Policy on Request receiving a array

I am facing a problem on a Request in which i am using a policy on it's authorize() method. It receives an array of IDs from the input request , which i then use for getting those from the DB and i have to confirm if a field called seller_id from one of those records matches the user logged. If so, o have to return false and i cannont keep on executing the job, if not i keep going. Not sure if i can pass that array as parameter so i tried the following option, which doesnt work. Any suggestions?
on the Request
public function authorize()
{
//return true;
$tickets = Ticket::find(request()->input('tickets_ids'))->get();
foreach($tickets as $ticket){
return request()->user()->can('buyTicket', $ticket);
}
}
on the Policy
public function buyTicket(User $user, Ticket $ticket){
if($user->id !== $ticket->seller_id){
return true;
}
return false;
}
Modify your request so it only aborts the loop if the current user can't buy the ticket. Otherwise, return true at the end of the function.
public function authorize()
{
$tickets = Ticket::findMany(request()->input('tickets_ids', []))->get();
foreach ($tickets as $ticket) {
$userCanBuyTicket = request()->user()->can('buyTicket', $ticket);
if (!$userCanBuyTicket) {
return false;
}
}
return true;
}

Laravel option only for the admin (permission)

I write a method, that which checks whether the user is an administrator in file UserController.php:
public function create(){
$chackIsAdmin = Auth::user()->permissions;
if ($chackIsAdmin === 1) {
return view('users.adduser');
} else {
return redirect('warehouse');
}
In table "users" I have column "permissions". Each user is assigned a number 1 or 0. 1 - is admin, 0 is NOT an admin.
I wrote also instruction if, which displays option "Add user" only the user administrator:
#if (Auth::user()->permissions === 1)
<li>Add new user</li>
#endif
It all works correctly, but I wonder whether in Laravel I can do it in a different way ??
Whether my way is safe?
I think the better way is to write the function on the User model.
class User extends Model
{
public function isAdmin()
{
return $this->attributes['permissions'] == 1;
}
}
Then it's very easy to use...
return Auth::user()->isAdmin() ? view('users.adduser') : redirect('warehouse');

Laravel relationship count()

I want to get a total user transaction (specific user) with relationship.
I've done it but i'm curious is my way is good approach.
//User Model
public function Transaction()
{
return $this->hasMany(Transaction::class);
}
//Merchant Model
public function Transaction()
{
return $this->hasMany(Transaction::class);
}
public function countTransaction()
{
return $this->hasOne(Transaction::class)
->where('user_id', Request::get('user_id'))
->groupBy('merchant_id');
}
public function getCountTransactionAttribute()
{
if ($this->relationLoaded('countTransaction'))
$this->load('countTransaction');
$related = $this->getRelation('countTransaction');
return ($related) ? (int)$related->total_transaction : 0;
}
//controller
$merchant = Merchant::with('countTransaction')->get();
What make me curious is part inside countTransaction. I put where where('user_id', Request::get('user_id')) directly inside the model.
is it good approach or any other way to get specific way?
expected result:
"merchant:"{
"name": "example"
"username" : "example"
"transactions": {
"count_transactions: "4" //4 came from a specific user.
}
}
I need to get the merchant data with the transaction count for specific user. This query is based on logged in user. so when a user access merchant page, they can see their transaction count for that merchant.
Thanks.
You really want to keep request data outside of your models (instead opting to pass it in). I'm also a little confused about why you have both a 'hasOne' for transactions, and a 'hasMany' for transactions within the merchant model.
I would probably approach the problem more like the below (untested, but along these lines). Again I'm not fully sure I understand what you need, but along these lines
// Merchant Model
public function transactions()
{
return $this->hasMany(Transaction::class);
}
public function countTransactionsByUser($userId)
{
return $this
->transactions()
->where('user_id', $userId)
->get()
->pluck('total_transaction')
->sum();
}
// Controller
$userId = request()->get('user_id');
// ::all() or however you want to reduce
// down the Merchant collection
//
$merchants = Merchant::all()->map(function($item, $key) {
$_item = $item->getAttributes();
$_item['transactions'] = [
'count_transactions' => $item->countTransactionsByUser($userId);
];
return $_item;
});
// Single total
// Find merchant 2, and then get the total transactions
// for user 2
//
$singleTotal = Merchant::find(2)
->countTransactionsByUser($userId);

Protect routes dynamically, based on id (laravel, pivot table)

This topic has been discussed a lot here, but I don't get it.
I would like to protect my routes with pivot tables (user_customer_relation, user_object_relation (...)) but I don't understand, how to apply the filter correctly.
Route::get('customer/{id}', 'CustomerController#getCustomer')->before('customer')
now I can add some values to the before filter
->before('customer:2')
How can I do this dynamically?
In the filter, I can do something like:
if(!User::hasAccessToCustomer($id)) {
App::abort(403);
}
In the hasAccessToCustomer function:
public function hasCustomer($id) {
if(in_array($id, $this->customers->lists('id'))) {
return true;
}
return false;
}
How do I pass the customer id to the filter correctly?
You can't pass a route parameter to a filter. However you can access route parameters from pretty much everywhere in the app using Route::input():
$id = Route::input('id');
Optimizations
public function hasCustomer($id) {
if($this->customers()->find($id)){
return true;
}
return false;
}
Or actually even
public function hasCustomer($id) {
return !! $this->customers()->find($id)
}
(The double !! will cast the null / Customer result as a boolean)
Generic approach
Here's a possible, more generic approach to the problem: (It's not tested though)
Route::filter('id_in_related', function($route, $request, $relationName){
$user = Auth::user();
if(!$user->{$relationName}()->find($route->parameter('id')){
App::abort(403);
}
});
And here's how you would use it:
->before('id_in_related:customers')
->before('id_in_related:objects')
// and so on

Check user permission in php

How can I create a PHP function or class that checks if a user who is a half-admin (set from a MySQL database) has some rights such as creating a new page, editing, or deleting?
I need a function that checks the user permissions and then display the code like this:
if ($he_can_create_page){
//continue the script.....
}else{
//don`t continue
}
In present I use sessions like this:
If($_SESSION['user_type']=='Admin'||$_SESSION['user_type']=='premium'){
//do stuff
}else if()......... {
// ..............
}
but they become too many if statements, and I want a cleaner code :)
interface User {
public function canCreatePage();
public function canDeletePage();
public function canEditPage();
....
}
class Admin implements User {
public function canCreatePage(){
return true;
}
public function canEditPage(){
return true;
}
...
}
class Editor implements User {
public function canCreatePage() {
return false;
}
public function canEditPage(){
return true;
}
...
}
then from what you get in the data base
if ($row['user_type'] == 'Admin') {
$user = new Admin();
} else if $row['user_type'] == 'Editor') {
$user = new Editor();
} ....
in all your pages :
if ($user->canCreatePage()){
//continue the script.....
}else{
//don`t continue
}
If you want to store your user in session the first time you get it from the dataBase
$_SESSION['user'] = serialize($user);
in the next page
$user = unserialize($_SESSION['user']);
Or you can also just store the id of the user in session and get it back from de
DB on every page.
Create a generic function an put it in a file which is common for all files something like this
function pageCreatePermission() {
if($_SESSION['user_type']=='Admin'||$_SESSION['user_type']=='premium'){
return true;
} else {
return false;
}
then use this function something like this in your file
if (pageCreatePermission()) {
//do your stuff
} else {
//show error you want
}
Add columns in your users table like:
| canEdit | canDelete | canCreate |
with flags 1/0. 1 for true, 0 for false.
select the fields and make checks i.e.:
if($row['canEdit'] = 1) {
//continue (return true)
}
else {
//stop (return false)
}
You can make it a function with params, so you will give the param to the function i.e. $canDelete (which is your $row data) and it checks only that permission
function userPermissions($type)
if($type=1) {
return true;
}
else {
return false;
}
$canCreate = $row['canCreate'];
if(userPermissions($canCreate)) { ...
The answer is to use an access control system. There are many different types. The most used (in web development) are ACL (Access control list) and RBAC (Role based access control). The rules can be filled from database or hardcoded.
To give you an idea of how they work look at the examples from Zend Framework: ACL and RBAC.
In Zend Framework the ACL is not very different from a RBAC because it also has roles. But normally an ACL is user based and not role based. If you like you can integrate the ACL/RBAC from Zend or other frameworks into your own project.
Read about how yii do it: yii RBAC

Categories