📋 Archival Copy: This is an archival copy of a blog post originally published on Adobe Magento DevBlog. The original publication date has been preserved.
From Magento 2.4, a controller action no longer has to extend anything. You implement \Magento\Framework\App\ActionInterface, or one of the method-specific interfaces such as HttpGetActionInterface, and inject only the dependencies you actually use instead of receiving the whole Context object. Dropping the classes that Context instantiated cuts CPU time on action generation by 5% for simple controllers, around 10% for common ones, and over 30% for complex ones.
Magento 2.4 became a perfect opportunity to proceed with backward-incompatible changes that were waiting for years. One such change was the decomposition of Controllers using composition instead of inheritance. When I spoke at Magento conferences about replacing inheritance with composition, I didn’t realize I would be part of making this change a reality.
The work began with this Pull Request and its followup introduced by Vinai Kopp. Although this change was very expected, these PRs were not merged initially.
At the beginning of 2020, Vinai encouraged me to continue the work on Controllers decomposition using his contribution. With his continuous support and tremendous work of Lena Orobei—together we finally delivered one of the biggest architectural changes towards decomposition of Controllers.
How do you write a controller action in Magento 2.4?
As a module developer, to implement a new Controller Action you only need to implement the \Magento\Framework\App\ActionInterface.
The authentication mechanisms for Customers are also migrated!
Why replace controller inheritance with composition?
The decomposition of Controllers brings several key benefits that improve both developer experience and application performance:
No need to extend
Module developers don’t have to extend from any class to create a fully functional action controller.
Example controller GET Action
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\View\Result\PageFactory;
class MyController implements HttpGetActionInterface
{
/** @var PageFactory */
protected $resultPageFactory;
public function __construct(PageFactory $resultPageFactory)
{
$this->resultPageFactory = $resultPageFactory;
}
public function execute()
{
return $this->resultPageFactory->create();
}
}
You may have noticed that we use \Magento\Framework\App\Action\HttpGetActionInterface. It is a method-specific Interface extending ActionInterface. If you want to explicitly define what methods are going to be handled by the Controller, the most common interfaces are:
\Magento\Framework\App\Action\HttpDeleteActionInterface\Magento\Framework\App\Action\HttpGetActionInterface\Magento\Framework\App\Action\HttpPostActionInterface\Magento\Framework\App\Action\HttpPutActionInterface
Please be aware that the HEAD method is handled the same way that GET is.
Performance
Previously, \Magento\Framework\App\Action\Context was injected into Actions with a set of classes:
/**
* @param \Magento\Framework\App\RequestInterface $request
* @param \Magento\Framework\App\ResponseInterface $response
* @param \Magento\Framework\ObjectManagerInterface $objectManager
* @param \Magento\Framework\Event\ManagerInterface $eventManager
* @param \Magento\Framework\UrlInterface $url
* @param \Magento\Framework\App\Response\RedirectInterface $redirect
* @param \Magento\Framework\App\ActionFlag $actionFlag
* @param \Magento\Framework\App\ViewInterface $view
* @param \Magento\Framework\Message\ManagerInterface $messageManager
* @param \Magento\Framework\Controller\Result\RedirectFactory $resultRedirectFactory
* @param \Magento\Framework\Controller\ResultFactory $resultFactory
*/
There’s no doubt that the new way of creating Controllers is much cleaner. The performance overhead caused by instantiating unused classes is significantly reduced.
Differences
| Controller | Example route | CPU time on generation |
|---|---|---|
| Simple | customer/account/logoutSuccess | 5% decrease |
| Common | catalog/category/view | 10% decrease |
| Complex | customer/section/load | > 30% decrease |
Performance measurements were performed using BlackFire.io, in an isolated Docker environment with cURL requests, not being affected by Browser/Network overhead. Thanks to Christophe Dujarric for BlackFire’s support.
These performance improvements demonstrate the tangible benefits of the composition approach, with CPU time reductions ranging from 5% for simple controllers to over 30% for complex ones.
Testing
Controllers are easier to test due to their reduced amount of dependencies.
Inheriting from AbstractAction forces you to use at least the same dependencies as the parent class. Unit Test for Category View has more than 70 lines of mocking dependencies, mocking Context methods to return mocked dependencies.
With the composition approach, you can inject dependencies directly into your class and inject only the ones you need (for example, only the ones you are going to use with your Unit Tests). This significantly reduces the complexity and verbosity of unit tests.
Caution
⚠️ Important Considerations:
- Keep in mind that some Modules have their own
AbstractAction. For example\Magento\Customer\Controller\AccountInterfaceadditionally handles Customer Authentication. - Controller “Supertypes” are deprecated (
\Magento\Backend\App\AbstractAction,\Magento\Framework\App\Action\Action,\Magento\Framework\App\Action\AbstractAction,Magento\Framework\App\Action\Action\AbstractAccount) and you should not use them anymore. - It is recommended to avoid code migration till 2.5.0 since third-party observers may be subscribed to your controllers. Methods like
getRequest,getResponse,getActionFlagare eliminated with the inheritance and it will lead to errors when accessing them through controller object from event. - It is recommended to use the new approach for new code only starting with the 2.4.0 release.
- Existing Magento controllers will not be migrated until 2.5.0 to keep backward compatibility.
FAQ
Do I still have to extend AbstractAction in Magento 2.4?
No. Implementing \Magento\Framework\App\ActionInterface is enough for a fully functional action controller. The controller supertypes (\Magento\Backend\App\AbstractAction, \Magento\Framework\App\Action\Action, \Magento\Framework\App\Action\AbstractAction, Magento\Framework\App\Action\Action\AbstractAccount) are deprecated and you should not use them anymore.
Which HTTP method interface should my controller implement?
Use the method-specific interface that matches the action: HttpGetActionInterface, HttpPostActionInterface, HttpPutActionInterface or HttpDeleteActionInterface. The HEAD method is handled the same way that GET is.
Should I migrate my existing controllers right away?
No. Use the new approach for new code only, starting with 2.4.0, and avoid migrating existing code until 2.5.0. Third-party observers may be subscribed to your controllers and call methods like getRequest, getResponse or getActionFlag, which are eliminated together with the inheritance.