Home > other >  Argument Issue On Laravel Policy Method
Argument Issue On Laravel Policy Method

Time:06-08

My controller method:

public function store(StoreTradeRequest $request, StoreTradeService $service)
{
    $this->authorize('store', [Trade::class, $request->all()]);

    // ...
}

My policy:

public function store(User $user, array $request): Response|bool
{
    return $user->id == $request['trader_id'];
}

My code works but I have a problem with the second argument of the authorize method in the contoller. As you can see, it's an array of two items while I need only the request on the policy side. I don't understand why, but if I send only the request, I'm getting the following error:

Too few arguments, 1 passed, 2 expected.

I have nothing to do with "Trade::class," and it can't reach policy method, but somehow I need it there to make it work. Any idea what is wrong here?

UPDATE I passed $request in every form I can imagine and the results are all the same.

'store', $request
'store', [$request]
'store', $request->all()
'store', $request->validated()
'store', [$request->all()]
'store', [$request->validated()]

CodePudding user response:

You can use optional type hint for user in your policy class like this:

public function store(?User $user, array $request): bool
{
    return true;
}

You can read about this here ;)

CodePudding user response:

How about this?

public function store(StoreTradeRequest $request, StoreTradeService $service)
{
    $this->authorize('store', [Trade::class, $request->all()]);
    // Change above line to below
    $this->authorize('store', $request->all());

    // ...
}

use HandlesAuthorization Trait like below :

use HandlesAuthorization;

// rest of the code look fine base on what you have in your mine.
public function store(User $user, array $request): bool
{
    return true;
}

// If user is guest, you can check for user too like below

// ?User $user
public function store(?User $user, array $request): bool

If by any chance you're trying to validate your data by accessing real data related to user, It is highly recommended to try other ways, since policies are meant to dictate access to models.

Edit 01 : Base on I didn't register it. My policy and model name are the same (Trade and TradePolicy), and Laravel discover it automatically., Comment.

GATE WITH POLICY WITHOUT MODEL

When you're trying to use policy by not using a model, you should use/register it in another way, because as i said, it meant to be used with a model, which in this case you're not using it and application want you to pass or inject it someway or another.

This is the regular way of registering policy which you should add your policy to protected $policies, which it is the way you said.

Laravel do the same way to discover your policy like as i explained.

So everything is right here, but the problem is you can't use it this way, you don't have model dedicated to this policy, so you can use Gate with policy.


class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
      // Don't add it here while you don't have model
      // Auto discover work like this method, so they're both same.
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        // Here you need to define a new gate related to policy.
        Gate::define('yourPolicy', [YourPolicy::class, 'yourPolicyMethod i guess']);
        Gate::define('yourPolicy', `App\Policies\YourPolicy::class`);
        Gate::define('yourPolicy', YourPolicy::class);
        // one of above ways base on laravel version.
        // or
    Gate::define('yourPolicy', function (User $user, Request $request){

    });
    }
}

As you can see after // or the way of defining gate is like policy, but, first way is your way.

And in controller :

public function store(StoreTradeRequest $request, StoreTradeService $service)
{

  if (! Gate::allows('yourPolicy', $request->all())) {
   ...
  }

}

Edit 02 :Plain Old PHP Object (POPO) with a little basic knowledge in OOP.

Plain Old PHP Object (POPO)

Alright, since you wanna stick to your method, I can only think of doing this in base idea of how to using models.

When i said policy meant to be to work with models, how about you don't have a common class?

Let's say, your policy don't need Trade, and it's only have purpose to handle request things, so this idea should be easy to do something like below :

class Request
{
   public array $request;

   public function __construct(array $request)
   {
      $this->request = $request;
   }
}

now let's create a new policy using php artisan make:policy RequestPolicy, and register it or test if it's auto discovered (in-case it didn't you can add it to protected $policies like below).

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        Request::class => RequestPolicy::class,
    ];
}

And then move your login to RequestPolicy:

class RequestPolicy
{
    public function store(User $user, Request $request): bool
    {
        return $user->id == $request['trader_id'];
    }
}

And of-course in Controller :

public function store(StoreTradeRequest $request, StoreTradeService $service)
{
    $this->authorize('store', new Request($request->(all)));

    // ...
}

NOW, in this way, which It's also a better way, you can recognize the purpose of policy, because it doesn't make sense if trade policy, got nothing to do with Trade model.

And in other-hand new policy make sense that it should handle things related to non-model things, the only problem is naming problem for your method, which in this case, instead of 'store' you can use something like 'storeTrade' or etc.

In this case, new policy will be flexible to other policies you're trying to achieve.

Of-course don't forget to check the Request namespace when you're trying to import it due to any conflict, which It should be like App\Models\Request.

  • Related