Controller layer in Extbase
The controller layer sits between the request and the view. It handles the request, coordinates repository and service calls, assigns variables for the view, and returns a response. Business logic can live in the controller directly — for anything complex or reused, a dedicated service class keeps the controller focused. Direct database queries belong in repositories, not in controllers.
On this page
Responses are not tied to Fluid
Fluid is the recommended way to render output but this is not a
hard requirement. Every action must return a
\Psr\ — what goes into that response is
entirely up to the action.
Calling
$this->html without arguments renders the Fluid
template and wraps it in a response. Passing a string to
$this->html skips Fluid and uses that string as the body
directly:
// Render the Fluid template for the current action
return $this->htmlResponse();
// Return a hand-built HTML string without Fluid
return $this->htmlResponse('<p>Hello world</p>');
// Return JSON
return $this->jsonResponse(json_encode(['count' => 42]));
// Redirect to another action (returns a redirect response, not a body)
return $this->redirect('list');
All of these satisfy the PSR-7
\Psr\ contract.
Returning anything other than a
\Psr\
from an action throws a
\Runtime at dispatch time.
Injecting dependencies into Extbase controllers
Dependencies are added to controllers via dependency injection. Two mechanisms are available:
Constructor injection is the standard approach. Declare dependencies as
protected readonly constructor parameters and the
DI container will provide them automatically:
use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class ConferenceController extends ActionController
{
public function __construct(
protected readonly ConferenceRepository $conferenceRepository,
) {}
}
Inject methods should be used when a controller extends a parent
class that already has a constructor. As PHP only allows one
constructor per class, additional dependencies cannot be added without
repeating the parent's parameter list. A public method named
inject with the dependency name will receive the dependency instead.
A typical case is a child controller that extends a base controller from your own extension or a third-party package. The base controller already owns the constructor, so the child uses inject methods for its own dependencies:
use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use MyVendor\MyExtension\Service\ConferenceService;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class ConferenceController extends ActionController
{
public function __construct(
protected readonly ConferenceRepository $conferenceRepository,
) {}
}
use MyVendor\MyExtension\Controller\ConferenceController;
use MyVendor\MyExtension\Service\ConferenceService;
class SpecialConferenceController extends ConferenceController
{
protected ConferenceService $conferenceService;
public function injectConferenceService(
ConferenceService $conferenceService,
): void {
$this->conferenceService = $conferenceService;
}
}
Inject methods are a fully supported DI pattern, not a fallback. Constructor injection is the recommended best practice — dependencies are declared in one place and immediately visible to anyone reading the class. Inject methods are the cleaner solution when a constructor is already owned by a parent class and cannot be extended without repeating its full parameter list.
Both mechanisms can be combined in the same class. Injected properties should
be
protected, not
private, so subclasses can access them.
In this chapter
- ActionController: actions, arguments and responses
- Actions, action arguments and automatic object resolution, response
helpers, redirect and forward, flash messages, per-action initializers,
#,[Authorize] #, and[Rate Limit] error.Action - Property mapping: request arguments to objects
- How raw request data is converted to typed PHP objects; the
__trustedPropertiesmechanism; when manual allowlisting ininitialize*Actionis necessary.()
See also
- Extbase validation
for validation rules on action arguments and model properties; how
validation failure feeds into
error.Action - Extbase view layer for Fluid integration, template resolution, and response types.