Create Controllers
Understanding controllers as entry points for custom functionality via URLs.
Controller Architecture
1. URL Structure and Routing
URL Breakdown
A standard Magento URL has three key segments:
| Segment | Configuration Source | Example |
|---|---|---|
| 1. Front Name | Defined in routes.xml |
blog |
| 2. Directory | Maps to directory in Controller/ |
post â Controller/Post/ |
| 3. Action Class | Maps to PHP class file | view â View.php |
URL: https://example.com/blog/post/view/id/123
File: Bonlineco/Blog/Controller/Post/View.php
bonlineco_blog_post_view= route_id + directory + action_class
Route Configuration
<!-- etc/frontend/routes.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<router id="standard">
<route id="bonlineco_blog" frontName="blog">
<module name="Bonlineco_Blog" />
</route>
</router>
</config>
2. Controller Anatomy
- Implement
ActionInterface(orHttpGetActionInterface) - Have
execute()method - Inject
Contextin constructor - Return
ResultInterfaceorHttpInterface
Controller Example
<?php
namespace Bonlineco\Blog\Controller\Post;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
class View implements HttpGetActionInterface
{
private $pageFactory;
private $context;
public function __construct(Context $context, PageFactory $pageFactory)
{
$this->context = $context;
$this->pageFactory = $pageFactory;
}
public function execute()
{
$postId = $this->context->getRequest()->getParam('id');
return $this->pageFactory->create();
}
}
3. Response Types
đ Page
PageFactory - Render HTML page
return $this->pageFactory->create();
âŠī¸ Redirect
RedirectFactory - 301/302 redirect
return $this->redirectFactory
->create()
->setPath('blog/post/list');
⊠Forward
ForwardFactory - Internal transfer
return $this->forwardFactory
->create()
->forward('noroute');
đ§ Raw
RawFactory - Arbitrary data
return $this->rawFactory
->create()
->setContents('Custom');
Don't return JSON from controllers - use REST API instead for authentication, structure, and Extension Attributes support.
4. Separation of Concerns
Best Practice Architecture
A key principle in Magento development is separating the handling of requests from data fetching and rendering.
- Minimal logic
- Call factories
- Return result
- Load from database
- Validate data
- Throw exceptions
- Format data
- Business logic
- Template helpers
Example: Clean Controller
Controller/Post/View.php - Minimal Logic
<?php
namespace Bonlineco\Blog\Controller\Post;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\View\Result\PageFactory;
use Bonlineco\Blog\Model\PostDisplayRequest;
class View implements HttpGetActionInterface
{
private $pageFactory;
private $postDisplayRequest;
public function __construct(
PageFactory $pageFactory,
PostDisplayRequest $postDisplayRequest
) {
$this->pageFactory = $pageFactory;
$this->postDisplayRequest = $postDisplayRequest;
}
public function execute()
{
// Validate request (throws NotFoundException if invalid)
$this->postDisplayRequest->validate();
// Just return the page - View Model handles data loading
return $this->pageFactory->create();
}
}
Data Class Example
Model/PostDisplayRequest.php - Data Loading & Validation
<?php
namespace Bonlineco\Blog\Model;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Exception\NotFoundException;
use Bonlineco\Blog\Api\PostRepositoryInterface;
class PostDisplayRequest
{
private $request;
private $postRepository;
public function __construct(
RequestInterface $request,
PostRepositoryInterface $postRepository
) {
$this->request = $request;
$this->postRepository = $postRepository;
}
/**
* Validate that the post exists and is active
* @throws NotFoundException
*/
public function validate()
{
$postId = $this->request->getParam('id');
if (!$postId) {
throw new NotFoundException(__('Post ID is required'));
}
$post = $this->postRepository->getById($postId);
if (!$post->getId()) {
throw new NotFoundException(__('Post not found'));
}
if (!$post->isActive()) {
throw new NotFoundException(__('Post is not available'));
}
}
/**
* Get the requested post
*/
public function getPost()
{
$postId = $this->request->getParam('id');
return $this->postRepository->getById($postId);
}
}
NotFoundException is a valid way to return a different response. Magento's exception handler catches it and displays a 404 page automatically.
View Model Example
ViewModel/PostView.php - Format Data for Template
<?php
namespace Bonlineco\Blog\ViewModel;
use Magento\Framework\View\Element\Block\ArgumentInterface;
use Bonlineco\Blog\Model\PostDisplayRequest;
class PostView implements ArgumentInterface
{
private $postDisplayRequest;
public function __construct(
PostDisplayRequest $postDisplayRequest
) {
$this->postDisplayRequest = $postDisplayRequest;
}
/**
* Get the post
*/
public function getPost()
{
return $this->postDisplayRequest->getPost();
}
/**
* Get formatted creation date
*/
public function getFormattedDate()
{
$post = $this->getPost();
return $post->getCreatedAt()->format('F j, Y');
}
/**
* Get post excerpt
*/
public function getExcerpt($length = 150)
{
$content = $this->getPost()->getContent();
return substr(strip_tags($content), 0, $length) . '...';
}
/**
* Check if post has featured image
*/
public function hasFeaturedImage()
{
return !empty($this->getPost()->getFeaturedImage());
}
}
<!-- In .phtml template -->
<?php /** @var $viewModel \Bonlineco\Blog\ViewModel\PostView */ ?>
<h1><?= $viewModel->getPost()->getTitle() ?></h1>
<p>Published: <?= $viewModel->getFormattedDate() ?></p>
<p><?= $viewModel->getExcerpt() ?></p>
5. Admin Controllers
Security is MANDATORY for Admin Controllers
Admin controllers require additional security measures to prevent unauthorized access.
Admin Controller Requirements
| Requirement | Details |
|---|---|
| 1. Route Location | etc/adminhtml/routes.xml (not frontend) |
| 2. Class Location | Controller/Adminhtml/... |
| 3. ACL Constant | const ADMIN_RESOURCE = 'Resource_Id'; (MANDATORY) |
| 4. Extend Backend Action | Extend \Magento\Backend\App\Action |
| 5. URL Access | Via generated links with secret keys |
1. Admin Route Configuration
etc/adminhtml/routes.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="admin">
<route id="bonlineco_blog" frontName="blog">
<module name="Bonlineco_Blog" before="Magento_Backend" />
</route>
</router>
</config>
https://example.com/admin/blog/post/edit/key/abc123Note the
/admin/ prefix and secret /key/abc123
2. Complete Admin Controller
Controller/Adminhtml/Post/Edit.php
<?php
namespace Bonlineco\Blog\Controller\Adminhtml\Post;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
class Edit extends Action
{
/**
* Authorization level - MANDATORY
* Maps to ACL resource in etc/acl.xml
*/
const ADMIN_RESOURCE = 'Bonlineco_Blog::post_save';
/**
* @var PageFactory
*/
private $pageFactory;
/**
* Constructor
*/
public function __construct(
Context $context,
PageFactory $pageFactory
) {
parent::__construct($context);
$this->pageFactory = $pageFactory;
}
/**
* Execute action
*/
public function execute()
{
$postId = $this->getRequest()->getParam('id');
$page = $this->pageFactory->create();
$page->getConfig()->getTitle()->prepend(__('Edit Post'));
return $page;
}
/**
* Optional: Override for custom authorization logic
*/
protected function _isAllowed()
{
// Check standard ACL
$isAllowed = parent::_isAllowed();
// Add custom checks
if ($isAllowed) {
$postId = $this->getRequest()->getParam('id');
// Custom logic: check if user can edit this specific post
// e.g., check ownership, status, etc.
}
return $isAllowed;
}
}
ADMIN_RESOURCE constant is checked automatically. If the admin user's role doesn't have permission for this resource, access is denied with a 403 error.
3. ACL Configuration
etc/acl.xml - Define Admin Resources
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
<acl>
<resources>
<resource id="Magento_Backend::admin">
<resource id="Bonlineco_Blog::blog" title="Blog" sortOrder="50">
<resource id="Bonlineco_Blog::post" title="Posts">
<resource id="Bonlineco_Blog::post_view" title="View Post" />
<resource id="Bonlineco_Blog::post_save" title="Save Post" />
<resource id="Bonlineco_Blog::post_delete" title="Delete Post" />
</resource>
</resource>
</resource>
</resources>
</acl>
</config>
- Define resources in
acl.xml - Assign resources to admin roles in System â User Roles
- Controller checks
ADMIN_RESOURCEagainst user's permissions - Access granted or denied based on role
4. Admin Menu Configuration
etc/adminhtml/menu.xml - Add Menu Items
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
<menu>
<add id="Bonlineco_Blog::blog"
title="Blog"
module="Bonlineco_Blog"
sortOrder="50"
resource="Bonlineco_Blog::blog"/>
<add id="Bonlineco_Blog::post_list"
title="Manage Posts"
module="Bonlineco_Blog"
sortOrder="10"
parent="Bonlineco_Blog::blog"
action="blog/post/index"
resource="Bonlineco_Blog::post_view"/>
</menu>
</config>
5. Generating Admin URLs in Code
When you need to generate admin URLs programmatically:
<?php
use Magento\Backend\Model\UrlInterface;
class Example
{
private $backendUrl;
public function __construct(UrlInterface $backendUrl)
{
$this->backendUrl = $backendUrl;
}
public function getEditUrl($postId)
{
return $this->backendUrl->getUrl(
'blog/post/edit',
['id' => $postId]
);
}
}
// Generated URL:
// https://example.com/admin/blog/post/edit/id/123/key/abc123def456
UrlInterface to ensure secret keys are included.
Exam Tips
- Know the 3 URL segments: frontName, directory, action class
- Controllers must implement ActionInterface and have execute() method
- Context must be injected in constructor
- Layout handle = route_id + directory + action
- Page, Redirect, Forward, Raw are main response types
- Don't return JSON from controllers - use REST API
- Separate concerns: Controller â Data Class â View Model
- Admin controllers need ADMIN_RESOURCE constant
- Admin URLs have secret keys, access via generated links
- Throw NotFoundException for missing resources