Controllers
Introductionβ
Controllers organize request-handling logic into dedicated classes,
instead of defining closures in route files. Controllers
are stored in app/Controllers/.
Bow supports two declaration styles:
- Routes defined in
routes/app.phpβ routes point to a controller using theController::actionsyntax (the style described throughout most of this document). - PHP 8 attribute routing β routes are declared directly on
the class with
#[Controller],#[Get],#[Post], etc. (see Attribute routing).
Creating a controllerβ
php bow add:controller UserController
Simple controllerβ
namespace App\Controllers;
use App\Models\User;
use Bow\Http\Request;
class UserController
{
public function index()
{
$users = User::all();
return view('users/index', compact('users'));
}
public function show(Request $request, int $id)
{
$user = User::retrieve($id);
return view('users/show', compact('user'));
}
public function store(Request $request)
{
$user = User::create([
'name' => $request->get('name'),
'email' => $request->get('email'),
]);
return redirect('/users/' . $user->id);
}
}
Defining the routesβ
$app->get('/users', 'UserController::index');
$app->get('/users/:id', 'UserController::show');
$app->post('/users', 'UserController::store');
Dependency injectionβ
Dependencies are automatically injected into constructors and methods:
namespace App\Controllers;
use App\Services\PaymentService;
use App\Services\NotificationService;
use Bow\Http\Request;
class OrderController
{
public function __construct(
private PaymentService $paymentService,
private NotificationService $notificationService
) {
}
public function store(Request $request)
{
$order = $this->paymentService->processOrder(
$request->only(['product_id', 'quantity'])
);
$this->notificationService->sendOrderConfirmation($order);
return response()->json($order, 201);
}
}
Invokable controllerβ
For single-action controllers, use __invoke:
namespace App\Controllers;
use App\Services\DashboardService;
use Bow\Http\Request;
class ShowDashboardController
{
public function __construct(
private DashboardService $dashboard
) {
}
public function __invoke(Request $request)
{
return view('dashboard', [
'stats' => $this->dashboard->getStats(),
]);
}
}
$app->get('/dashboard', ShowDashboardController::class);
Namespacesβ
For nested controllers, use the relative path:
// Controller: App\Controllers\Admin\UserController
$app->get('/admin/users', 'Admin\UserController::index');
Generate a nested controller:
php bow add:controller Admin/UserController
Middlewareβ
Apply middleware to routes:
$app->get('/profile', 'ProfileController::show')->middleware('auth');
// Multiple middleware
$app->get('/admin', 'AdminController::index')->middleware(['auth', 'admin']);
REST controllerβ
REST controllers make it easy to build RESTful APIs.
Generating a REST controllerβ
php bow generate:resource ArticleController
Generated structureβ
namespace App\Controllers;
use Bow\Http\Request;
class ArticleController
{
/**
* GET /articles
*/
public function index(): void
{
// List of articles
}
/**
* POST /articles
*/
public function store(Request $request): void
{
// Create an article
}
/**
* GET /articles/:id
*/
public function show(Request $request, mixed $id): void
{
// Display an article
}
/**
* PUT /articles/:id
*/
public function update(Request $request, mixed $id): void
{
// Update an article
}
/**
* DELETE /articles/:id
*/
public function destroy(Request $request, mixed $id): void
{
// Delete an article
}
}
Registering REST routesβ
$app->rest('articles', 'ArticleController');
Generated routesβ
| URL | Method | Action | Name |
|---|---|---|---|
/articles | GET | index | articles.index |
/articles | POST | store | articles.store |
/articles/:id | GET | show | articles.show |
/articles/:id | PUT | update | articles.update |
/articles/:id | DELETE | destroy | articles.destroy |
The update method is registered for PUT only. If you also want
to expose PATCH (partial update), add a dedicated route:
$app->patch('/articles/:id', 'ArticleController::update')
->where('id', '\d+');
The name is built from str_replace('/', '.', $url) . '.' . $action.
With a URL that has a leading slash such as '/api/v1/articles', you will get
names like .api.v1.articles.index (leading dot). Pass the URL without
a leading slash ('api/v1/articles') for clean names.
Parameter constraintsβ
// Global constraint
$app->rest('articles', 'ArticleController', ['id' => '\d+']);
// Per-method constraints
$app->rest('articles', 'ArticleController', [
'show' => ['id' => '\d+'],
'update' => ['id' => '\d+'],
]);
Ignoring methodsβ
$app->rest('articles', [
'controller' => 'ArticleController',
'ignores' => ['destroy'],
], ['id' => '\d+']);
Controllers with attributesβ
To avoid maintaining a central route file, you can declare routes directly on the class using PHP 8 attributes:
namespace App\Controllers;
use Bow\Http\Request;
use Bow\Router\Attributes\{Controller, Get, Post, Put, Delete};
#[Controller(prefix: '/articles', middleware: ['auth'], name: 'articles.')]
final class ArticleController
{
#[Get('/', name: 'index')]
public function index() { /* ... */ }
#[Get('/:id', name: 'show', where: ['id' => '\d+'])]
public function show(Request $request, int $id) { /* ... */ }
#[Post('/', name: 'store', middleware: ['validate'])]
public function store(Request $request) { /* ... */ }
#[Put('/:id', name: 'update')]
public function update(Request $request, int $id) { /* ... */ }
#[Delete('/:id', name: 'destroy')]
public function destroy(Request $request, int $id) { /* ... */ }
}
Then all you need to do is register the class in the route file:
$app->register(\App\Controllers\ArticleController::class);
// Or a batch of controllers
$app->register([
\App\Controllers\ArticleController::class,
\App\Controllers\CommentController::class,
]);
The name: prefix of #[Controller] is concatenated verbatim to the name of
each route β choose 'articles.' (with a dot) to get
articles.index, articles.show, etc. See Routing > Attribute routing
for the full list of attributes (#[Patch], #[Options], #[Route]β¦).
Namespace configurationβ
Customize component namespaces in app/Kernel.php:
public function namespaces(): array
{
return [
'controller' => 'App\\Controllers',
'middleware' => 'App\\Middlewares',
'listener' => 'App\\Listeners',
];
}
| Key | Used by |
|---|---|
controller | Resolving 'UserController::action' β App\Controllers\UserController, php bow add:controller, generate:resource |
middleware | php bow add:middleware |
listener | php bow add:listener |
Is something missing?
If you run into problems with the documentation or have suggestions to improve the documentation or the project in general, please open an issue for us, or send a tweet mentioning the Twitter account @bowframework or directly on github.