Organizing Logic with Controllers
Controllers handle HTTP requests and return responses. Keep them thin by delegating business logic to services.
Resource Controller
php artisan make:controller PostController --resource --model=Post
class PostController extends Controller
{
public function index(): View
{
$posts = Post::with(['author', 'category'])
->published()
->latest()
->paginate(12);
return view('posts.index', compact('posts'));
}
public function show(Post $post): View
{
// Route model binding automatically finds the post
$post->load(['comments.user', 'tags']);
return view('posts.show', compact('post'));
}
public function store(StorePostRequest $request): RedirectResponse
{
$post = Post::create($request->validated());
return redirect()
->route('posts.show', $post)
->with('success', 'Post created successfully.');
}
public function update(UpdatePostRequest $request, Post $post): RedirectResponse
{
$post->update($request->validated());
return redirect()
->route('posts.show', $post)
->with('success', 'Post updated.');
}
public function destroy(Post $post): RedirectResponse
{
$post->delete();
return redirect()
->route('posts.index')
->with('success', 'Post deleted.');
}
}
Form Request Validation
Never validate inline. Always use Form Request classes:
php artisan make:request StorePostRequest
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return true; // Or check user permissions
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'slug' => ['required', 'string', 'unique:posts,slug'],
'body' => ['required', 'string', 'min:100'],
'category_id' => ['required', 'exists:categories,id'],
'published_at' => ['nullable', 'date', 'after:now'],
];
}
public function messages(): array
{
return [
'body.min' => 'Post content must be at least 100 characters.',
];
}
}
Form Requests keep controllers clean and validation logic reusable and testable.