<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class PostPolicy
{
public function before(User $user, string $ability): ?bool
{
if ($user->isAdmin()) {
return true; // Admins can do anything
}
return null; // Continue to policy methods
}
public function viewAny(User $user): bool
{
return true;
}
public function view(?User $user, Post $post): bool
{
// Anyone can view published posts
if ($post->isPublished()) {
return true;
}
// Only author can view drafts
return $user && $user->id === $post->user_id;
}
public function create(User $user): bool
{
return $user->hasVerifiedEmail();
}
public function update(User $user, Post $post): Response
{
return $user->id === $post->user_id
? Response::allow()
: Response::deny('You do not own this post.');
}
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
public function restore(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
}
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function show(Post $post)
{
// Throws 403 if unauthorized
$this->authorize('view', $post);
return view('posts.show', compact('post'));
}
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$post->update($request->validated());
return redirect()->route('posts.show', $post);
}
public function destroy(Post $post)
{
// Can also use Gate facade
if (\Gate::denies('delete', $post)) {
abort(403);
}
$post->delete();
return redirect()->route('posts.index');
}
}
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Edit Post</a>
@endcan
@can('delete', $post)
<form action="{{ route('posts.destroy', $post) }}" method="POST">
@csrf
@method('DELETE')
<button type="submit">Delete Post</button>
</form>
@endcan
@cannot('update', $post)
<p>You cannot edit this post.</p>
@endcannot
Policies organize authorization logic around models, keeping permission checks clean and reusable. Each policy method corresponds to an action—view, create, update, delete. I call policies via the Gate facade or authorize() helper in controllers. The before() method grants admin users universal access. Policies integrate with form requests via the authorize() method. For actions not tied to models, I use gates. The @can Blade directive conditionally shows UI elements based on authorization. Policy methods receive the authenticated user and optionally the model instance. Resource policies provide CRUD methods automatically. This pattern centralizes permission logic, making it easy to audit and modify access control.