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
- Centralized control: One point for authentication, logging, error handling
- URL routing: Single place to map URLs to handlers
- Security: Cross-cutting concerns handled in one place
- 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?
2. What happens if no router matches a request in Magento?
3. How can you intercept the dispatch process?
Flashcards
Question
What is the Front Controller pattern?
Click to reveal answer
Answer
All requests go through a single entry point that dispatches to appropriate handlers
Question
What does the FrontController dispatch method do?
Click to reveal answer
Answer
Routes request to appropriate controller, executes action, returns response
Question
How to intercept dispatch?
Click to reveal answer
Answer
Plugin on Magento\Framework\App\FrontController (before/after/aroundDispatch)
Question
What if no router matches?
Click to reveal answer
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