Reputation: 1178
i'm using Laravel as my PHP framework. its a convention to put index show store ...
functions in controllers.
i have 2 types of users(Admin & normal user). lets assume there is an Order
(in restaurant) model and i want to implement index
function for its controller.
a user can have more than one Order.
what i need is that this function:
- if admin is calling this API: returns all Orders
- if normal user is calling this API: returns just Orders owned by this user
i searched but i couldn't find anything(tbh i didn't know what to search).
once i did this as below which i didn't like because it looks two different functions gathered in one:
if ($user->role == admin) {
// fetch all orders
} else if ($user->role == normal_user) {
// just find user orders
}
so my question is what's best approach to achieve what i want?
Upvotes: 7
Views: 4065
Reputation: 20594
Such a REST API endpoint is typically a search allowing multiple filters, sorting and pagination. If so it is completly fine to apply different defaults for filters and also restrict filters to roles.
I would auto apply a filter user=currentUser for missing admin role and return a forbidden if a user without the admin role tries to apply a user filter for a different user.
With this approach you give admins also the functionality to search for offers of a specific user and you only need one search api to be used by the controller.
Upvotes: 2
Reputation: 3567
I had a similar question myself a while ago and ended up with this strange solution to avoid that if/else block.
I assumed the existence of an helper method in the User model called isNot($role)
to verify the if the user's role matches or not the given one.
This is just an example to give the idea of the check, but you should implement the condition as you like.
Second assumption I made is that each order has a user_id
field which will reference the owner of that order though his id (FK of 1:N among user and order).
public function index(Request $request)
{
$orders = Order::query()
->when($request->user()->isNot('admin'), function ($query) use ($request) {
return $request->user()->orders();
// Or return $query->where('user_id', $request->user()->id);
})
->paginate();
return OrderResource::collection($orders);
}
The when
method is the key here. Basically you call it like: when($value, $callback)
and if $value
is false
the callback won't be executed, otherwise it will.
So for example, if the user is not an admin, you will end up executing this query:
Order::paginate();
that would fetch all the order with pagination (note that you could swap paginate
with get
.
Otherwise, the callback is gonna be executed and you will execute the paginate
method on $request->user()->orders();
(orders called like a method is still a query builder object, so you can call paginate on it).
The query would be:
$request->user()->orders()->paginate();
If you instead opted for the second solution in the callback you would basically add a where condition (filtering on the user_id of the orders) to the main scope to get only the user's orders.
The query would be:
Order::query()->where('user_id', $request->user()->id)->paginate();
Finally, to better control what's sent back as response I use Laravel's API Resource (and I really suggest you to do so as well if you need to customize the responses).
NOTE: The code might have syntax and/or logical errors as it was just an on the fly edit from production code, and it hasn't been tested, but it should give an overall idea for a correct implementation.
Upvotes: 1
Reputation: 559
You can create scope in Order class... For example you have field user_id in Order, for detect user
class Order
{
...
public function scopeByRole($query)
{
if (!Auth::user()->isAdmin())
$query = $query->where('user_id', Auth::user()->id);
return $query;
}
}
in you controller just get all Orders with scope:
$orders = Order::byRole()->get();
it return you orders by you role
Also you need have in class User function for detect role, example
class User
{
public function isAdmin()
{
// you logic which return true or false
}
}
Upvotes: 0
Reputation: 1733
it would be better to include the if/else in your order modal like this:
class Order extends Model {
....
static function fetchFor (User $user) : Collection
{
return $user->isAdmin() ? self::all() : self::where("user_id",$user->id);
}
}
then you can call this method on your controller
public function index()
{
return view('your-view')->with('orders',Order::fetchFor(Auth::user())->get())
}
Upvotes: 0
Reputation: 751
Why don't use an if statement?
You could make a scope on the model but then you'll still have an if.
What about this?
if ($user->role == admin) {
Order::all();
} else if ($user->role == normal_user) {
$user->orders()->get();
}
Or make it an inline if
$user->role == admin ? Order::all() : $user->orders()->get();
IMO the best practice here is to make a different Admin/OrderController.php
Then with middleware check wat, the role of the user is, and then redirect them to the admin controllers.
Since you'll probably also want an update and delete, or other functions only accesible by an Admin
Upvotes: 1