Beyond a basic controller, Laravel's resource controller convention and the (generally discouraged) pattern of calling one controller's method from another are both worth understanding — the latter especially for recognizing when it signals a design that should be refactored.
Generating a basic controller
php artisan make:controller ProductController
class ProductController extends Controller
{
public function index()
{
$products = Product::paginate(10);
return view('products.index', compact('products'));
}
public function show(Product $product)
{
return view('products.show', compact('product'));
}
}
Generating a resource controller, with every CRUD method stubbed out
php artisan make:controller ProductController --resource
This generates index, create, store, show, edit, update, and destroy methods already stubbed out — matching Laravel's conventional set of seven RESTful actions for a resource, saving the effort of writing each method signature manually.
Registering all seven routes in one line
Route::resource('products', ProductController::class);
php artisan route:list --name=products
Route::resource() registers all seven conventional routes (matching HTTP verb, URL pattern, and controller method) in a single line — significantly more concise than defining each of the seven routes individually with separate Route::get()/post()/put()/delete() calls.
Registering only specific resource routes
Route::resource('products', ProductController::class)->only(['index', 'show']);
Route::resource('products', ProductController::class)->except(['destroy']);
Genuinely useful when a resource shouldn't expose the full set of seven actions — a public-facing product catalog, for instance, typically only needs index and show, with the create/edit/delete actions living instead behind a separate admin-only controller.
An API resource controller (excludes create/edit, which return HTML forms)
php artisan make:controller Api/ProductController --api
Route::apiResource('products', Api\ProductController::class);
--api and apiResource() both skip generating and registering the create and edit methods/routes — these two exist purely to return an HTML form for creating or editing a resource, which has no meaning for a pure JSON API that doesn't render Blade views at all.
Calling one controller's method from another
class OrderController extends Controller
{
public function checkout(Request $request)
{
$productController = app(ProductController::class);
$stockCheck = $productController->checkStock($request->productId);
// ...
}
}
Resolving another controller through the container (via app()) and calling one of its methods directly does technically work — but it's usually a signal that the shared logic (checkStock here) actually belongs in a dedicated service class instead, injectable independently into both controllers, rather than creating a direct dependency between two controllers that otherwise have no natural relationship.
The cleaner alternative: extracting shared logic into a service
class StockService
{
public function checkStock(int $productId): bool
{
return Product::find($productId)?->stock > 0;
}
}
class OrderController extends Controller
{
public function checkout(Request $request, StockService $stockService)
{
$stockCheck = $stockService->checkStock($request->productId);
}
}
Following the custom service class pattern covered elsewhere on this site, both ProductController and OrderController can independently inject StockService — this avoids the awkward, generally discouraged controller-to-controller dependency entirely, while still sharing the exact same underlying logic between them.