Skip to content
intermediate Phase 14 · Application Structure

Front Controller Pattern

Front controller pattern in Magento. Magento\Framework\App\FrontController, how it dispatches to action controllers

45m
0 problems
Topic Progress 0%

The Front Controller Pattern

What is the Front Controller Pattern?

The Front Controller pattern handles all incoming requests through a single entry point, which dispatches to appropriate handlers.

All Requests → FrontController → Router → Action Controller → Response

Benefits

  1. Centralized control: One point for authentication, logging, error handling
  2. URL routing: Single place to map URLs to handlers
  3. Security: Cross-cutting concerns handled in one place
  4. Extensibility: Interceptors can modify the dispatch chain

Magento's FrontController

namespace Magento\Framework\App;

class FrontController
{
    public function __construct(
        private array $routers,
        private ManagerInterface $eventManager,
        private AppInterface $app
    ) {}

    public function dispatch(RequestInterface $request): ResponseInterface
    {
        // 1. Check if already dispatched (prevent double dispatch)
        if ($this->app->isDispatched()) {
            return $this->app->getResponse();
        }

        // 2. Fire event (plugins can modify request)
        $this->eventManager->dispatch('controller_action_predispatch', [
            'request' => $request,
        ]);

        // 3. Try each router until one matches
        $routerFound = false;
        foreach ($this->routers as $router) {
            $actionInstance = $router->match($request);
            if ($actionInstance !== null) {
                $routerFound = true;
                break;
            }
        }

        if (!$routerFound) {
            // No router matched → 404
            return $this->prepareNotFoundResponse();
        }

        // 4. Execute the action
        $result = $actionInstance->dispatch($request);

        // 5. Return response
        return $result;
    }
}

Router Interface

namespace Magento\Framework\App\Router\Action\ListInterface;

interface ActionListInterface
{
    public function getActionClass($routeName, $routeModule);
}

// Standard router resolves:
// frontName → module → controller → action
// catalog → Magento_Catalog → Product → View
// = Magento\Catalog\Controller\Product\View

Action Controller Dispatch

namespace Magento\Framework\App\Action;

abstract class Action
{
    public function dispatch(RequestInterface $request): ResponseInterface
    {
        $this->request = $request;
        $this->_eventPrefix = $this->getRequest()->getRouteName();

        // Fire pre-dispatch event
        $this->_eventManager->dispatch(
            'controller_action_predispatch_' . $this->getRequest()->getFullActionName(),
            ['controller_action' => $this]
        );

        // Execute the action (implemented by subclass)
        $result = $this->execute();

        // Fire post-dispatch event
        $this->_eventManager->dispatch(
            'controller_action_postdispatch_' . $this->getRequest()->getFullActionName(),
            ['controller_action' => $this]
        );

        return $result;
    }

    abstract public function execute();
}

Request and Response Objects

Request Object

namespace Magento\Framework\App\Request;

class Http implements RequestInterface
{
    // Get all parameters
    public function getParams(): array

    // Get specific parameter
    public function getParam(string $key, $default = null)

    // Set parameter
    public function setParam(string $key, $value): Http

    // Get HTTP method
    public function getMethod(): string

    // Get request URI
    public function getRequestUri(): string

    // Get action name (from URL)
    public function getActionName(): string

    // Get controller name (from URL)
    public function getControllerName(): string

    // Get module name (from URL)
    public function getModuleName(): string

    // Get full action name: module_controller_action
    public function getFullActionName(): string
}

// Usage in controller:
$productId = $this->getRequest()->getParam('id');
$page = $this->getRequest()->getParam('p', 1);
$search = $this->getRequest()->getParam('q');

Response Object

namespace Magento\Framework\App\Response;

class Http implements ResponseInterface
{
    // Set HTTP status code
    public function setStatusCode(int $code): Http

    // Set response header
    public function setHeader(string $name, string $value, bool $replace = false): Http

    // Set response body
    public function setBody(string $content): Http

    // Set redirect URL
    public function setRedirect(string $url, int $code = 302): Http

    // Send response to client
    public function sendResponse(): void
}

// Usage:
$this->getResponse()->setStatusCode(404);
$this->getResponse()->setHeader('Content-Type', 'application/json');
$this->getResponse()->setBody(json_encode(['error' => 'Not found']));

Plugins on the FrontController

Intercepting the Dispatch Process

<!-- etc/di.xml -->
<config>
    <type name="Magento\Framework\App\FrontController">
        <plugin name="vendor_dispatch_log"
                type="Vendor\Module\Plugin\FrontControllerPlugin"
                sortOrder="10"/>
    </type>
</config>
namespace Vendor\Module\Plugin;

class FrontControllerPlugin
{
    public function __construct(
        private \Psr\Log\LoggerInterface $logger
    ) {}

    // Before dispatch: log all requests
    public function beforeDispatch(
        \Magento\Framework\App\FrontController $subject,
        \Magento\Framework\App\RequestInterface $request
    ): void {
        $this->logger->info('Request dispatched', [
            'uri' => $request->getRequestUri(),
            'method' => $request->getMethod(),
            'route' => $request->getRouteName(),
        ]);
    }

    // After dispatch: log response time
    public function afterDispatch(
        \Magento\Framework\App\FrontController $subject,
        $result
    ) {
        $this->logger->info('Dispatch completed', [
            'status' => $result->getStatusCode(),
        ]);
        return $result; // Must return the result!
    }
}

Around Dispatch: Modify Request

public function aroundDispatch(
    \Magento\Framework\App\FrontController $subject,
    callable $proceed,
    \Magento\Framework\App\RequestInterface $request
) {
    // Before: modify request
    if ($request->getParam('force_ssl') && !$request->isSecure()) {
        // Force HTTPS
        return $this->redirect->redirect($request, 'https://' . $request->getHttpHost() . $request->getRequestUri());
    }

    // Call original dispatch
    $result = $proceed($request);

    // After: modify response
    $result->setHeader('X-Custom-Header', 'value');

    return $result;
}

Controller-Level Plugins

<!-- Plugin on specific controller -->
<type name="Magento\Catalog\Controller\Product\View">
    <plugin name="vendor_product_view_log"
            type="Vendor\Module\Plugin\ProductViewPlugin"/>
</type>
public function beforeExecute(\Magento\Catalog\Controller\Product\View $subject)
{
    // Runs before Product\View::execute()
    $productId = $subject->getRequest()->getParam('id');
    $this->logger->info('Product view: ' . $productId);
}

public function afterExecute(\Magento\Catalog\Controller\Product\View $subject, $result)
{
    // Runs after Product\View::execute()
    $this->analytics->trackPageView('product', $productId);
    return $result;
}

Quiz

1. What is the purpose of the Front Controller pattern?

Question 1 options

2. What happens if no router matches a request in Magento?

Question 2 options

3. How can you intercept the dispatch process?

Question 3 options

Flashcards

Question

What is the Front Controller pattern?

Answer

All requests go through a single entry point that dispatches to appropriate handlers

Question

What does the FrontController dispatch method do?

Answer

Routes request to appropriate controller, executes action, returns response

Question

How to intercept dispatch?

Answer

Plugin on Magento\Framework\App\FrontController (before/after/aroundDispatch)

Question

What if no router matches?

Answer

FrontController returns 404 response

Revision Notes

Key Takeaways

  • 1. Front Controller: all requests through one entry point, dispatched to controllers
  • 2. Router chain: Standard → CMS → UrlRewrite → Default (404)
  • 3. Request object provides params, URI, method, route information
  • 4. Response object provides status, headers, body, redirect
  • 5. Plugins can intercept dispatch process at FrontController or Controller level
  • 6. Events: controller_action_predispatch and controller_action_postdispatch

Interview Tips

  • Explain the Front Controller pattern and why it's used
  • Trace what happens when a URL doesn't match any route (404)
  • Discuss how plugins can modify the dispatch process

Cheat Sheet

Front Controller Pattern:
  All requests → FrontController → Router → Controller → Action → Response

Dispatch Flow:
  1. Check if already dispatched
  2. Fire predispatch event
  3. Try routers: Standard → Cms → UrlRewrite → Default
  4. Execute action controller
  5. Fire postdispatch event
  6. Return response

Request: getParam(), getMethod(), getRequestUri(), getActionName()
Response: setStatusCode(), setHeader(), setBody(), sendResponse()

Plugins: beforeDispatch(), afterDispatch(), aroundDispatch()
Events: controller_action_predispatch, controller_action_postdispatch