Originally published on laravel.learnio.dev — part of the Laravel best practices course. View this lesson on the original site.
Use one class for one endpoint responsibility
Single action controllers are invokable classes with one __invoke() method. They are useful when a route performs one focused operation.
Create an invokable controller
php artisan make:controller PublishOrderController --invokable
Laravel creates a controller class with a single handler method.
Register the route without a method string
use App\Http\Controllers\PublishOrderController;
use Illuminate\Support\Facades\Route;
Route::patch('/orders/{order}/publish', PublishOrderController::class)
->middleware(['auth'])
->name('orders.publish');
You pass only the class name because Laravel calls __invoke() automatically.
Example controller shape
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PublishOrderController extends Controller
{
public function __invoke(Request $request, Order $order): RedirectResponse
{
// domain action or service call here
$order->update(['status' => 'published']);
return to_route('orders.show', ['order' => $order->id]);
}
}
When this is a strong choice
- the endpoint maps to one clear action,
- you want explicit route intent in
web.phporapi.php, - you want smaller controller files with less unrelated code.
Bad way vs better way
Bad way (one large controller with many unrelated actions):
class OrderController extends Controller
{
public function publish(...) {}
public function archive(...) {}
public function resendInvoice(...) {}
public function syncWithErp(...) {}
}
Better way (one action per focused endpoint when complexity grows):
Route::patch('/orders/{order}/publish', PublishOrderController::class);
Route::patch('/orders/{order}/archive', ArchiveOrderController::class);
Route::post('/orders/{order}/resend-invoice', ResendOrderInvoiceController::class);
Why better:
- each class has one responsibility,
- route-to-behaviour mapping is easier to read,
- tests stay focused and faster to maintain.
Keep naming predictable
Use action-oriented names:
PublishOrderControllerArchiveOrderControllerResendInvoiceController
This helps developers understand intent without opening each file.
Invokable controller flow
flowchart LR
A[Route points to class] --> B[Laravel calls __invoke]
B --> C[Single focused action]
C --> D[Redirect JSON or view response]
Next, you will combine this with dependency injection patterns so complex actions can stay thin and testable.
From Laravel best practices — Routing, Controllers & Request Flow