Controllers
Controllers are regular PHP classes that handle an HTTP action and return an ActionResponse. They are resolved from the PSR-11 container, so constructor injection works out of the box.
Defining a controller
<?php
declare(strict_types=1);
namespace App\MyApp\Controllers;
use PhpMvc\Controllers\Controller;
use PhpMvc\Actions\Responses\ActionResponse;
use PhpMvc\Actions\Responses\View;
use PhpMvc\Actions\Responses\RedirectTo;
use PhpMvc\Actions\Responses\LocalRedirectTo;
final class ArticleController extends Controller
{
public function __construct(
private readonly ArticleRepository $articles,
) {}
public function index(): ActionResponse
{
$articles = $this->articles->findAll();
return $this->view(model: (object) ['articles' => $articles]);
}
public function show(int $id): ActionResponse
{
$article = $this->articles->findById($id);
return $this->view(model: $article);
}
public function store(ArticleRequest $request): ActionResponse
{
$this->articles->save($request);
return $this->redirectToAction('index');
}
}
ActionResponse subtypes
View
Renders an HTML template. The view path is resolved relative to the configured views directory.
// Renders Views/Article/index.html (convention: ControllerName/action)
return $this->view();
// Explicit path (no .html extension)
return $this->view(name: 'Article/list');
// Pass a model
return $this->view(model: $article);
The Controller::view() helper infers the view path from the calling class and method name when not given explicitly.
RedirectTo
Redirects to an absolute URL (http/https only):
return RedirectTo::create(url: 'https://example.com/external');
return RedirectTo::create(url: 'https://example.com/new-path', args: ['ref' => 'old']);
The status code is always 302 Found.
LocalRedirectTo
Redirects to another controller/action within the same app. The URL is resolved automatically — you never hardcode paths.
Use the redirectToAction() helper from Controller:
// Same controller, no args
return $this->redirectToAction('index');
// Different controller, with args
return $this->redirectToAction(
action: 'show',
controller: ArticleController::class,
args: (object) ['id' => $newId],
);
Or call LocalRedirectTo::create() directly when outside a controller:
return LocalRedirectTo::create(
action: 'show',
controller: ArticleController::class,
args: (object) ['id' => $newId],
);
Wiring to routes
Controllers are referenced by their fully-qualified class name in Route::create(). There is no annotation magic — the link between a route and a controller is explicit:
Route::create(
RouteMethod::Get,
Path::create('/articles/{int:id}'),
ArticleController::class, // FQCN
'show', // method name as string
);
Action parameter resolution
Action arguments are resolved automatically from the request:
- Route path parameters — type-coerced from the URL.
- Query string — scalar keys mapped by parameter name.
- Parsed body — for
POST/PUT/PATCH/DELETErequests. ServerRequestInterface— injected automatically if the action declares it.- DTO objects — non-scalar parameters are constructed from request input.
See Request Binding for the full binding rules.