ActionController

Most Extbase controllers are based on the \TYPO3\CMS\Extbase\Mvc\Controller\ActionController. It is theoretically possible to base a controller directly on the \TYPO3\CMS\Extbase\Mvc\Controller\ControllerInterface, however there are rarely use cases for that. Implementing the ControllerInterface does not guarantee a controller to be dispatchable. It is not recommended to base your controller directly on the ControllerInterface.

Actions

Most public and protected methods that end in "Action" (for example indexAction() or showAction()), are automatically registered as actions of the controller.

Changed in version 11.0: To comply with PSR standards, controller actions should return an instance of the Psr\Http\Message\ResponseInterface. This becomes mandatory with TYPO3 v12.0.

Many of these actions have parameters. You should use strong types for the parameters as this is necessary for the validation.

Class T3docs\BlogExample\Controller\BlogController
use Psr\Http\Message\ResponseInterface;
use T3docs\BlogExample\Domain\Model\Blog;
use TYPO3\CMS\Extbase\Annotation\IgnoreValidation;

class BlogController extends AbstractController
{
    /**
     * Displays a form for creating a new blog
     *
     * @IgnoreValidation("newBlog")
     */
    public function newAction(?Blog $newBlog = null): ResponseInterface
    {
        $this->view->assign('newBlog', $newBlog);
        $this->view->assign(
            'administrators',
            $this->administratorRepository->findAll()
        );
        return $this->htmlResponse();
    }
}

The validation of domain object can be explicitly disabled by the annotation @TYPO3\CMS\Extbase\Annotation\IgnoreValidation. This might be necessary in actions that show forms or create domain objects.

Default values can, as usual in PHP, just be indicated in the method signature. In the above case, the default value of the parameter $newBlog is set to NULL.

If the action should render the view you can return $this->htmlResponse() as a shortcut for taking care of creating the response yourself.

In order to redirect to another action, return $this->redirect('another'):

Class T3docs\BlogExample\Controller\BlogController
use Psr\Http\Message\ResponseInterface;
use T3docs\BlogExample\Domain\Model\Blog;
use T3docs\BlogExample\Exception\NoBlogAdminAccessException;
use TYPO3\CMS\Extbase\Annotation\Validate;

class BlogController extends AbstractController
{
    /**
     * Updates an existing blog
     *
     * $blog is a not yet persisted clone of the original blog containing
     * the modifications
     *
     * @Validate(param="blog", validator="T3docs\BlogExample\Domain\Validator\BlogValidator")
     * @throws NoBlogAdminAccessException
     */
    public function updateAction(Blog $blog): ResponseInterface
    {
        $this->checkBlogAdminAccess();
        $this->blogRepository->update($blog);
        $this->addFlashMessage('updated');
        return $this->redirect('index');
    }
}

If an exception is thrown while an action is executed you will receive the "Oops an error occurred" screen on a production system or a stack trace on a development system with activated debugging.

Note

The methods initializeAction(), initializeDoSomethingAction() and errorAction() have special meanings in initialization and error handling and are no Extbase actions.

Define initialization code

Sometimes it is necessary to execute code before calling an action. For example, if complex arguments must be registered, or required classes must be instantiated.

There is a generic initialization method called initializeAction(), which is called after the registration of arguments, but before calling the appropriate action method itself. After the generic initializeAction(), if it exists, a method named initialize[ActionName](), for example initializeShowAction is called.

In this method you can perform action specific initializations.

In the backend controller of the blog example the method initializeAction() is used to discover the page that is currently activated in the page tree and save it in a variable:

Class T3docs\BlogExample\Controller\BackendController
class BackendController extends ActionController
{
    protected function initializeAction()
    {
        $this->pageUid = (int)($this->request->getQueryParams()['id'] ?? 0);
        parent::initializeAction();
    }
}

Catching validation errors with errorAction

If an argument validation error has occurred, the method errorAction() is called.

The default implementation sets a flash message, error response with HTTP status 400 and forwards back to the originating action.

This is suitable for most actions dealing with form input.

If you need a to handle errors differently this method can be overridden.

Hint

If a domain object should not be validated, for example in the middle of an editing process, the validation of that object can be disabled by the annotation @TYPO3\CMS\Extbase\Annotation\IgnoreValidation.

Forward to a different controller

It is possible to forward from one controller action to an action of the same or a different controller. This is even possible if the controller is in another extension.

This can be done by returning a \TYPO3\CMS\Extbase\Http\ForwardResponse.

In the following example, if the current blog is not found in the index action of the PostController, we follow to the list of blogs displayed by the indexAction of the BlogController.

Class T3docs\BlogExample\Controller\PostController
use Psr\Http\Message\ResponseInterface;
use T3docs\BlogExample\Domain\Model\Blog;
use TYPO3\CMS\Core\Pagination\SimplePagination;
use TYPO3\CMS\Extbase\Http\ForwardResponse;
use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator;

class PostController extends \T3docs\BlogExample\Controller\AbstractController
{
    /**
     * Displays a list of posts. If $tag is set only posts matching this tag are shown
     */
    public function indexAction(
        ?Blog $blog = null,
        string $tag = '',
        int $currentPage = 1
    ): ResponseInterface {
        if ($blog == null) {
            return (new ForwardResponse('index'))
                ->withControllerName(('Blog'))
                ->withExtensionName('blog_example')
                ->withArguments(['currentPage' => $currentPage]);
        }
        if (empty($tag)) {
            $posts = $this->postRepository->findByBlog($blog);
        } else {
            $tag = urldecode($tag);
            $posts = $this->postRepository->findByTagAndBlog($tag, $blog);
            $this->view->assign('tag', $tag);
        }
        $paginator = new QueryResultPaginator($posts, $currentPage, 3);
        $pagination = new SimplePagination($paginator);
        $this->view
            ->assign('paginator', $paginator)
            ->assign('pagination', $pagination)
            ->assign('pages', range(1, $pagination->getLastPageNumber()))
            ->assign('blog', $blog)
            ->assign('posts', $posts);
        return $this->htmlResponse();
    }
}