The life of a request
In Magento a request goes through the FrontController, the router list and the action's execute(). In Symfony HttpKernel follows the same path, but every step is an event anyone can subscribe to: routing, security, argument conversion, exception handling. Understand this diagram, and most of Symfony's "magic" becomes ordinary event listeners.
flowchart TD
A["public/index.php"] --> B["Kernel::handle(Request)"]
B --> C{{"kernel.request"}}
C -->|"RouterListener finds the route"| C2["Firewall: who the user is"]
C2 --> D{{"kernel.controller"}}
D --> E["ArgumentResolver: Request, entities, DTOs"]
E --> F["Controller"]
F -->|"returned a Response"| H{{"kernel.response"}}
F -->|"returned something else"| G{{"kernel.view"}}
G --> H
H --> I["Response::send()"]
I --> J{{"kernel.terminate"}}
F -. "exception" .-> X{{"kernel.exception"}}
X --> H
| Symfony step | Closest in Magento | What happens there |
|---|---|---|
| public/index.php, Kernel | pub/index.php, Bootstrap, App\Http::launch() | Loading the compiled container. |
| kernel.request: RouterListener (priority 32) | FrontController::dispatch(), RouterList::match() | The route is written into the Request attributes. |
| kernel.request: Firewall (priority 8) | area + session, admin authentication plugin | Authentication, the user token. |
| kernel.controller | controller_action_predispatch, plugins on dispatch() | This is where #[IsGranted], #[Cache] fire. |
| ArgumentResolver | none; getParam() by hand | An entity by id, a DTO from JSON, the current user. |
| Controller | ActionInterface::execute() | Your code. |
| kernel.view | none | Turning a non-Response into a Response, e.g. #[Template]. |
| kernel.response | controller_front_send_response_before | Headers, cookies, the Web Profiler toolbar. |
| kernel.terminate | none | Work after the response is sent: logs, emails. |
| kernel.exception | ExceptionHandler, noroute | 404/403/500, error pages. |
To see all listeners and their priorities: bin/console debug:event-dispatcher kernel.request.