Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

bionary's avatar

Policies and FormRequests

I do love laravel but as the training videos age and the Laravel versions change the techniques the instructors use to accomplish tasks tends to be very different over time.

I'm reading the docs regarding policies (Ver.9) and they recommending applying a policy in the controller using something like:

	public function create(ArtworkRequest $request, User $user){
		$this->authorize('create', Artwork::class);
		direct()->route('artworks.index');
	}

And then I'm watching a Laravel Cookbook video & reading the docs about using the validaton() method inside a FormRequest class.

Which is better and why? I actually dislike the options and find "well it depends" statements in the training to be increasingly unhelpful and frustrating.

And while I'm on the topic of FormRequests, does anybody think the -R flag for creating FormRequests while creating a controller is a bit redundant? php artisan make:controller TestController -R

Why?

The convenient -R flag creates two form request files:

  • Store Model Request
  • Update Model Request

That means you are duplicating your logic in two separate files for essentially the same task: saving a record. Of course the rules for the model will be the same for both inserting and updating, so why does Laravel think having two separate FormRequest files is the way to go?

Perhaps I'm missing something.

0 likes
7 replies
jlrdw's avatar

Authorization and validation are two different things.

Edit:

If you have authorization properly implemented, a non authorized user will never make it to that form to fill out.

SilenceBringer's avatar

@bionaryk to answer to your question you need to pay attention to this section: https://laravel.com/docs/9.x/validation#authorizing-form-requests

I think Laravel creates 2 different form request because they may require different permission to perform, so, authorize method will be different for create and update

By the way, as for me I prefer to have authorization logic in controller and have just 1 FormRequest class for create and update. Much easier to read and maintain, I think

1 like
Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

Where to put the authorization is one of those things that is mostly personal choice. I don't think it has any reason to be in a form request and would never put it there. For me form requests are for validation. But I rarely use the authorize method either as that allows validation to run. Instead I put them on the route directly as middleware, to stop the request early.

In regards to two form requests. The reason is that they are not the in a lot of cases. Imagine working with posts. The name of the post is unique. But when you update you want to ignore the post itself. Same is true for all unique fields. Or maybe a profile picture is required when creating a profile, but not updating it. That said, people often add extra logic to just use 1 form request for both. So if you prefer that just delete 1 or create one seperately

bionary's avatar

Good insight @silencebringer & @sinnbeck Thank you both for taking the time to explain things!

Although I don't like bloated looking routes in my web.php file I agree that authorization should really take place prior to any form validation. And since type-hinted FormRequests in the controller are run first it looks like using: $this->authorize('create', Artwork::class); in the controller is something I don't prefer. (too bad)

So that means I will have to authorize via middleware in the routes file. I did some experimenting via methods: ->can() and ->middleware() and need some further guidance.

To give background of app I'll provide some additional code. My policies will govern the main types of users (ACL) I'm using Spatie's laravel-permissions package of course.

  • Super-Admin (using gate, policies don't apply)
  • Admin
  • Artist
  • Guest

Admin will be able to CRUD on any artists' artwork App\Models\Artwork Artists will only be able to CRUD on their own.

#web.php
Route::middleware(['auth'])->group(function () {
	Route::get('artwork',[ArtworkController::class, 'index'] )
			->name('artworks.index');
	Route::post('artwork/{user}',[ArtworkController::class, 'create'] )
					->name('artworks.create')
					->middleware('can:create,App\Models\Artwork');
});

A Gate as recommended by docs (Laravel & Spatie) is used for Super-Admin

Users: Admin & Artist are authorized by policies

Guest is authorized by auth middlewhere

I'm wrapping all the registered-user-logic in Route::middleware(['auth'])->group(function () {... Then I'm applying individual policy middleware to each corresponding route. What I have works now, but is this the proper way to do this?

BTW: I tried applying middleware using ->can() and it did NOT work (And I don't know why):

Route::middleware(['auth'])->group(function () {
	Route::get('artwork',[ArtworkController::class, 'index'] )
								->name('artworks.index');
	Route::post('artwork/{user}',[ArtworkController::class, 'create'] )
								->name('artworks.create')
								->can('create', Artwork::class);
});

My Artwork Policy...

#ArtworkPolicy
 public function create(User $user)
    {
		//Admin: Creating artwork for self or any user
		if($user->can('create all artwork')){
			return true;
		}

		//Artist: Creating one's own artwork
		if(auth()->id() === request()->user->id){
			return $user->can('create own artwork');
		}
    }

I needed a way to prevent an Artist from creating Artwork under a different user; hence this:

if(auth()->id() === request()->user->id){ 
...

The policy didn't respect passing in a user model.

Sinnbeck's avatar

@bionary this is very close to what I do myself. I use the same pattern. If I were to create something similar, I'm sure I would do the exact same thing. For broad authorizations (artists can see artist profile page) I would just pass the check directly to the package

1 like
bionary's avatar

My final piece to the puzzle is the route names / conventions. This is typically no big deal if a simple web app, but with the ability of an Admin to see the index route of any user I'm at another fork in the road.

Option one is to always supply a User like:

#Passing User
Route::get('user/{user}/artworks', [ArtworkController::class, 'index'] )
   ->name('artworks.index')
   ->can('viewAny', Artwork::class);

Option two is to use query strings with only admin use to choose the desired user's index page

#Passing Query Strings
Route::get('artworks', [ArtworkController::class, 'index'] )
   ->name('artworks.index')
   ->can('viewAny', Artwork::class);

Option one is probably the most straight forward but oddly displays the user's id to them for seemingly no reason (to them). In short it makes the url longer and uglier.

Option two presents some extra if/else logic in the controller

I played around with making {user?} optional, but I didn't like that much, it's easy to easily mess up the routes as the app grows and also requires some additional if/else stuff in the controller.

Any ideas?

Sinnbeck's avatar

@bionary I would pick another unique field like username and go with 1

Route::get('user/{user:username}/artworks

Or you can add 2 routes that point to the same endpoint

1 like

Please or to participate in this conversation.