Front Controller: the guy who handles the request in Magento 2

If you’re a developer who has some experience in coding in Magento you probably have met this class before and, maybe you noticed its importance in the application, maybe not. The fact is, the Front Controller in Magento 2 plays a huge responsibility in the Magento 2 application and I’m gonna talk a little bit about it right below.

In Magento 2, the Front Controller is a fundamental component of the application’s request-handling process. It’s responsible for receiving incoming HTTP requests, routing them to the appropriate controllers, and generating corresponding responses. The Fron Controller pattern is implemented in Magento 2 to handle the request-response flow and provide a central entry point for processing client requests, so if you ever heard about the Magento 2 Request Flow process, you probably saw that this class is one of the most important pieces of it.

The Front Controller Design Pattern

Before digging into how it’s implemented in Magento 2, we first need to understand that this idea didn’t come from nothing. The Front Controller is a design pattern in software architectural patterns and is commonly used in web applications. It provides a centralized entry point for handling incoming requests and dispatching them to the appropriate components for processing. The key idea behind the Front Controller pattern is to have a single controller responsible for handling all requests, rather than having multiple controllers scattered throughout the application.

Hare are the key components and characteristics of the Front Controller pattern:

  1. Single Entry Point: The Front Controller serves as the single entry point for all requests coming into the application. It receives requests from clients, such as web browsers, and then routes them to the appropriate handlers based on the request information.
  2. Request Routing: The Front Controller examines the request details, such as the URL or other parameters, to determine which components should handle the request. It may use routing configuration or rules to map requests to specific controllers or actions.
  3. Request Handling: Once the Front Controller identifies the appropriate handler (typically a controller), it delegates the request to that handler for further processing. The handler performs the necessary actions, such as interacting with the application’s models, manipulating data, or preparing a response.
  4. Centralized Functionality: The Front Controller consolidates common functionality and tasks that are shared across different requests. This can include tasks like authentication, logging, error handling, or data validation. By centralizing such functionality, the Front Controller promotes code reuse and reduces duplication.
  5. Extensibility: The Front Controller pattern allows for easy extensibility and customization. Developers can introduce plugins, interceptors, or middleware at various stages of the request processing to modify the behavior of the Front Controller or its handlers. This enables additional functionality to be added without modifying the core implementation.
  6. Response Generation: After the request is processed by the appropriate handler, the Front Controller may participate in generating the response. This can involve rendering views, assembling the response data, and returning the response to the client.

The Front Controller pattern provides several benefits, including a clear separation of concerns, improved maintainability, and the ability to apply cross-cutting concerns uniformly. It helps manage the request lifecycle, enforce consistent request handling, and simplify the overall architecture of web applications.

Many web frameworks and content management systems (including Magento 2) adopt the Front Controller pattern as a foundational element for handling client requests and achieving a structured and modular application design.

The Magento 2 Front Controller Implementation

In Magento 2, this controller is typically represented by the \Magento\Framework\App\FrontControllerInterface interface which is implemented by the concrete class \Magento\Framework\App\FrontController. It’s packed with the magento/framework composer package, so it’s found under the vendor/magento/framework directory on your Magento installation. The base method is the dispatch method, which is the following:

    /**
     * Perform action and generate response
     *
     * @param RequestInterface|HttpRequest $request
     * @return ResponseInterface|ResultInterface
     * @throws \LogicException
     * @throws LocalizedException
     */
    public function dispatch(RequestInterface $request)
    {
        Profiler::start('routers_match');
        $this->validatedRequest = false;
        $routingCycleCounter = 0;
        $result = null;
        while (!$request->isDispatched() && $routingCycleCounter++ < 100) {
            /** @var \Magento\Framework\App\RouterInterface $router */
            foreach ($this->_routerList as $router) {
                try {
                    $actionInstance = $router->match($request);
                    if ($actionInstance) {
                        $result = $this->processRequest(
                            $request,
                            $actionInstance
                        );
                        break;
                    }
                } catch (\Magento\Framework\Exception\NotFoundException $e) {
                    $request->initForward();
                    $request->setActionName('noroute');
                    $request->setDispatched(false);
                    break;
                }
            }
        }
        Profiler::stop('routers_match');
        if ($routingCycleCounter > 100) {
            throw new \LogicException('Front controller reached 100 router match iterations');
        }
        return $result;
    }

However, other than the request for static files, which are handled by the file pub/get.php, every request is initially processed by the pub/index.php file. The index.php file initializes the Magento application, sets up the environment, and then hands over the dispatch of the request to the \Magento\Framework\App\Http::launch method. In turn, this method gets an instance of the Front Controller and calls the method dispatch, described above.

$frontController = $this->_objectManager->get(\Magento\Framework\App\FrontControllerInterface::class);
$result = $frontController->dispatch($this->_request);

The importance of the Front Controller in Magento 2 lies in its role in managing the entire request lifecycle. Here are some key aspects and functionalities provided by the Front Controller in Magento 2:

  1. Routing: The Front Controller determines which controller and action should handle a specific request based on the URL and routing configuration (etc/frontend/routes.xml or etc/adminhtml/routes.xml). It maps the incoming request to a specific module, controller, and action class.
  2. Request Processing: Once the appropriate controller and action are determined, the Front Controller invokes the corresponding controller class and executes the requested action.
  3. Plugin Execution: The Front Controller supports the execution of plugins (also known as interceptors in Magento 2) before, around, and after the dispatch process. Plugins allow developers to modify the behavior of controllers and perform additional actions or validations.
  4. Error Handling: The Front Controller handles exceptions and errors that occur during the request processing and provides an appropriate response or redirects the user to an error page if necessary.
  5. Response Generation: After the controller action is executed, the Front Controller plays a role in generating the response, including rendering the appropriate view templates and returning the response to the client.

By implementing the Front Controller pattern, Magento 2 achieves a modular and extensible architecture for handling client requests. It allows for centralized request processing, consistent routing, and the ability to customize and extend the behavior of controllers using plugins. Overall, the Front Controller is a critical component that facilitates the flow of requests and responses in a Magento 2 application.

If we analyze the code from the dispatch method in the Front Controller class, we can see that it’s actually simple enough to understand. I’ll comment on the most important block on it.

Here we have some variable and profiler initialization.

        Profiler::start('routers_match');
        $this->validatedRequest = false;
        $routingCycleCounter = 0;
        $result = null;

Then we have a while loop until we have the $request object set as dispatched or the $routingCycleCounter reaches the limit of 100 iterations.

while (!$request->isDispatched() && $routingCycleCounter++ < 100) {

In the body of the loop, we have the following code.

/** @var \Magento\Framework\App\RouterInterface $router */
foreach ($this->_routerList as $router) {
    try {
        $actionInstance = $router->match($request);
        if ($actionInstance) {
            $result = $this->processRequest(
                $request,
                $actionInstance
            );
            break;
        }
    } catch (\Magento\Framework\Exception\NotFoundException $e) {
        $request->initForward();
        $request->setActionName('noroute');
        $request->setDispatched(false);
        break;
    }
}

This iterates the routers available in the $_routerList (\Magento\Framework\App\RouterList) property of the class calling the method match of each one. If an action instance is returned by any of them, the process is handed over to the private method processRequest.

/**
 * Process (validate and dispatch) the incoming request
 *
 * @param RequestInterface $request
 * @param ActionInterface $actionInstance
 * @return ResponseInterface|ResultInterface
 * @throws LocalizedException
 *
 * @throws NotFoundException
 */
private function processRequest(
    RequestInterface $request,
    ActionInterface $actionInstance
) {
    $request->setDispatched(true);
    $this->response->setNoCacheHeaders();
    $result = null;

    //Validating a request only once.
    if (!$this->validatedRequest) {
        $area = $this->areaList->getArea($this->appState->getAreaCode());
        $area->load(Area::PART_DESIGN);
        $area->load(Area::PART_TRANSLATE);

        try {
            $this->requestValidator->validate($request, $actionInstance);
        } catch (InvalidRequestException $exception) {
            //Validation failed - processing validation results.
            $this->logger->debug(
                sprintf('Request validation failed for action "%s"', get_class($actionInstance)),
                ["exception" => $exception]
            );
            $result = $exception->getReplaceResult();
            if ($messages = $exception->getMessages()) {
                foreach ($messages as $message) {
                    $this->messages->addErrorMessage($message);
                }
            }
        }
        $this->validatedRequest = true;
    }

    // Validation did not produce a result to replace the action's.
    if (!$result) {
        $this->dispatchPreDispatchEvents($actionInstance, $request);
        $result = $this->getActionResponse($actionInstance, $request);
        if (!$this->isSetActionNoPostDispatchFlag()) {
            $this->dispatchPostDispatchEvents($actionInstance, $request);
        }
    }

    //handling redirect to 404
    if ($result instanceof NotFoundException) {
        throw $result;
    }
    return $result;
}

This method loads the proper area of the application for translations and design and validates the request. If the validation doesn’t result in any error, the request is set as validated ($this->validatedRequest = true;) and the action response is returned by calling the method $this->getActionResponse($actionInstance, $request).

/**
 * Return the result of processed request
 *
 * There are 3 ways of handling requests:
 * - Result without dispatching event when FLAG_NO_DISPATCH is set, just return ResponseInterface
 * - Backwards-compatible way using `AbstractAction::dispatch` which is deprecated
 * - Correct way for handling requests with `ActionInterface::execute`
 *
 * @param ActionInterface $actionInstance
 * @param RequestInterface $request
 * @return ResponseInterface|ResultInterface
 * @throws NotFoundException
 */
private function getActionResponse(ActionInterface $actionInstance, RequestInterface $request)
{
    if ($this->actionFlag->get('', ActionInterface::FLAG_NO_DISPATCH)) {
        return $this->response;
    }

    if ($actionInstance instanceof AbstractAction) {
        return $actionInstance->dispatch($request);
    }

    return $actionInstance->execute();
}

From here on, the process is handled by the controller action class that matched the given request, and the response is returned to the rendering system.

One thought on “Front Controller: the guy who handles the request in Magento 2

  1. Pingback: Invalid Form Key: why does that happen? – Tiago Sampaio

Leave a comment