Controller Class Hierarchy
Magento controllers follow a specific class hierarchy.
Class hierarchy:
ActionInterface
└── AbstractAction (Magento\Framework\App\Action\AbstractAction)
├── Action (Magento\Framework\App\Action\Action) — Frontend
└── Backend\App\Action (Magento\Backend\App\Action) — Admin
└── Backend\App\Action\AbstractMassAction
Frontend controller:
<?php
namespace Vendor\Blog\Controller\Post;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
class View extends Action
{
public function __construct(
Context $context,
private \Vendor\Blog\Model\PostFactory $postFactory
) {
parent::__construct($context);
}
public function execute()
{
$id = $this->getRequest()->getParam('id');
$post = $this->postFactory->create()->load($id);
if (!$post->getId()) {
$this->messageManager->addErrorMessage(__('Post not found'));
return $this->_redirect('*/*/index');
}
$this->_view->getLayout()->registerScript(
'Vendor_Blog::post/view.phtml'
);
$this->_view->renderPage();
}
}
Admin controller:
<?php
namespace Vendor\Blog\Controller\Adminhtml\Post;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
class Edit extends Action
{
const ADMIN_RESOURCE = 'Vendor_Blog::blog_posts';
public function __construct(
Context $context,
private \Magento\Backend\Model\View\ResultPageFactory $resultPageFactory
) {
parent::__construct($context);
}
public function execute()
{
$resultPage = $this->resultPageFactory->create();
$resultPage->getConfig()->getTitle()->set(__('Edit Post'));
return $resultPage;
}
}
Frontend vs Admin Controllers
Frontend and admin controllers have different base classes and capabilities.
Frontend controller features:
- Extends Action (Magento\Framework\App\Action\Action)
- Access to _view for layout rendering
- Session and cookie management
- Customer authentication optional
- Area: frontend
Admin controller features:
- Extends Backend\App\Action
- ACL permission checking via ADMIN_RESOURCE
- Admin session authentication required
- Access to admin UI components
- Area: adminhtml
Comparison:
// Frontend\Action has:
$this->_view // View factory
$this->_redirect() // Redirect helper
$this->messageManager // Session messages
$this->getRequest() // Request object
$this->getResponse() // Response object
// Backend\App\Action has everything above plus:
$this->_auth // Admin auth
$this->_authorization // ACL checking
$this->_adminSession // Admin session
$this->_backendUrl // Admin URL builder
Non-action controllers:
// Controllers that don't follow standard action pattern
// Used for: APIs, exports, imports, special handlers
<?php
namespace Vendor\Module\Controller;
use Magento\Framework\App\Action\Action;
class Export extends Action
{
public function execute()
{
// Set response headers
$this->getResponse()->setHeader('Content-Type', 'text/csv');
$this->getResponse()->setHeader('Content-Disposition', 'attachment; filename="export.csv"');
// Output directly
$this->getResponse()->setBody($csvContent);
return $this->getResponse();
}
}
AbstractAction and Shared Logic
AbstractAction provides common functionality for all controllers.
What AbstractAction provides:
// In your controller constructor
public function __construct(
Context $context // Required: includes all shared dependencies
) {
parent::__construct($context);
}
// Context provides:
// - $this->_view (View)
// - $this->messageManager (ManagerInterface)
// - $this->_redirect (RedirectInterface)
// - $this->getRequest() (RequestInterface)
// - $this->getResponse() (ResponseInterface)
// - $this->_eventManager (EventManager)
// - $this->_storeManager (StoreManagerInterface)
// - $this->_logLogger (Logger)
Creating base controller for shared logic:
<?php
namespace Vendor\Blog\Controller;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
class AbstractPost extends Action
{
public function __construct(
Context $context,
protected \Vendor\Blog\Model\PostFactory $postFactory,
protected \Vendor\Blog\Service\PostService $postService
) {
parent::__construct($context);
}
protected function getPost(): ?\Vendor\Blog\Model\Post
{
$id = $this->getRequest()->getParam('id');
if (!$id) {
return null;
}
$post = $this->postFactory->create()->load($id);
return $post->getId() ? $post : null;
}
protected function validatePost(\Vendor\Blog\Model\Post $post): bool
{
if (!$post->getTitle()) {
$this->messageManager->addErrorMessage(__('Title is required'));
return false;
}
return true;
}
}
// Use in controller
class Save extends AbstractPost
{
public function execute()
{
$post = $this->getPost();
// ...
}
}
Controller Best Practices
Best practices for controller implementation.
1. Keep controllers thin:
// BAD: Business logic in controller
class Save extends Action
{
public function execute()
{
// 100+ lines of business logic
}
}
// GOOD: Delegate to service class
class Save extends Action
{
public function __construct(
Context $context,
private \Vendor\Module\Service\SaveService $saveService
) {
parent::__construct($context);
}
public function execute()
{
try {
$this->saveService->save($this->getRequest()->getParams());
$this->messageManager->addSuccessMessage(__('Saved successfully'));
} catch (\Exception $e) {
$this->messageManager->addErrorMessage($e->getMessage());
}
return $this->_redirect('*/*/index');
}
}
2. Always return ResultInterface:
// Return types:
return $this->resultFactory->create(
\Magento\Framework\View\Result\Page::class
); // Page result
return $this->resultRedirectFactory->create()
->setPath('*/*/index'); // Redirect result
return $this->resultFactory->create(
\Magento\Framework\Controller\Result\Json::class
)->setData($data); // JSON result
3. Use proper HTTP methods:
// GET: Read operations
class View extends Action { /* ... */ }
// POST: Write operations
class Save extends Action { /* ... */ }
// DELETE: Delete operations
class Delete extends Action { /* ... */ }
4. Handle errors gracefully:
public function execute()
{
try {
// Process
$this->messageManager->addSuccessMessage(__('Success'));
} catch (\Magento\Framework\Exception\LocalizedException $e) {
$this->messageManager->addErrorMessage($e->getMessage());
} catch (\Exception $e) {
$this->messageManager->addErrorMessage(__('An error occurred'));
$this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e);
}
return $this->_redirect('*/*/index');
}
Quiz
1. What class do frontend controllers extend?
2. What constant defines admin resource in admin controllers?
3. What provides common dependencies to all controllers?
4. What should controllers return?
Flashcards
Question
What class do frontend controllers extend?
Click to reveal answer
Answer
Magento\Framework\App\Action\Action
Question
What class do admin controllers extend?
Click to reveal answer
Answer
Magento\Backend\App\Action
Question
What does ADMIN_RESOURCE define?
Click to reveal answer
Answer
The ACL resource required for admin controller access
Question
What provides shared controller dependencies?
Click to reveal answer
Answer
Magento\Framework\App\Action\Context
Question
What result types are available?
Click to reveal answer
Answer
Page, Json, Redirect, Raw, Forward
Revision Notes
Key Takeaways
- 1. Frontend controllers extend Action; admin controllers extend Backend\App\Action
- 2. Context provides shared dependencies to all controllers
- 3. ADMIN_RESOURCE constant controls admin ACL permissions
- 4. Controllers should be thin — delegate business logic to services
- 5. Always return ResultInterface (Page, Json, Redirect)
- 6. Handle exceptions with messageManager
Interview Tips
- • Explain the difference between frontend and admin controllers
- • Describe the controller class hierarchy
- • Know how to implement shared logic in base controllers
- • Discuss error handling patterns in controllers
Cheat Sheet
Controllers Cheat Sheet
Frontend: extends Action
Admin: extends Backend\App\Action
Context provides:
- messageManager
- _redirect
- _view
- getRequest/getResponse
- _eventManager
ADMIN_RESOURCE:
const ADMIN_RESOURCE = 'Vendor_Module::resource';
Return types:
- Page (view)
- Json (API)
- Redirect (redirect)
- Forward (internal forward)
Keep controllers thin → delegate to services