Page Result Actions
IndexAction - Listing Page
<?php
namespace Amazon\Prep\Controller\Index;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Result\Page;
use Magento\Framework\Result\PageFactory;
class Index implements HttpGetActionInterface
{
public function __construct(
private PageFactory $resultPageFactory
) {}
public function execute(): Page
{
$page = $this->resultPageFactory->create();
$page->getConfig()->getTitle()->set(__('Prep Items'));
return $page;
}
}
ViewAction - Detail Page
<?php
namespace Amazon\Prep\Controller\Item;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Result\Page;
use Magento\Framework\Result\PageFactory;
use Magento\Framework\Controller\Result\RedirectFactory;
use Magento\Framework\Message\ManagerInterface;
class View implements HttpGetActionInterface
{
public function __construct(
private PageFactory $resultPageFactory,
private RedirectFactory $resultRedirectFactory,
private ManagerInterface $messageManager
) {}
public function execute()
{
$itemId = (int)$this->getRequest()->getParam('id');
if (!$itemId) {
$this->messageManager->errorMessage(__('Invalid item ID.'));
return $this->resultRedirectFactory->create()
->setPath('amazon_prep/index/index');
}
$page = $this->resultPageFactory->create();
$page->getConfig()->getTitle()->set(__('View Item'));
$page->getLayout()->getBlock('item.view')
->setData('item_id', $itemId);
return $page;
}
}
NewAction - Create Form
<?php
namespace Amazon\Prep\Controller\Item;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Result\Page;
use Magento\Framework\Result\PageFactory;
class NewAction implements HttpGetActionInterface
{
public function __construct(
private PageFactory $resultPageFactory
) {}
public function execute(): Page
{
$page = $this->resultPageFactory->create();
$page->getConfig()->getTitle()->set(__('New Prep Item'));
return $page;
}
}
JSON Result Actions
AJAX Search Endpoint
<?php
namespace Amazon\Prep\Controller\Ajax;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Registry;
class Search implements HttpGetActionInterface
{
public function __construct(
private JsonFactory $resultJsonFactory,
private Registry $registry
) {}
public function execute()
{
$query = $this->getRequest()->getParam('q', '');
$storeId = $this->registry->registry('store')
->getId();
$results = $this->searchProducts($query, $storeId);
$result = $this->resultJsonFactory->create();
$result->setData([
'success' => true,
'results' => array_map(function ($item) {
return [
'id' => $item->getId(),
'name' => $item->getName(),
'price' => $item->getPrice(),
'url' => $item->getUrl(),
];
}, $results),
'count' => count($results),
]);
return $result;
}
private function searchProducts(string $query, int $storeId): array
{
// Search implementation
return [];
}
}
JSON Response with Headers
public function execute()
{
$result = $this->resultJsonFactory->create();
$result->setData(['status' => 'ok']);
$result->setHttpResponseCode(200);
$result->setHeader('Cache-Control', 'no-cache', true);
return $result;
}
Error JSON Response
public function execute()
{
try {
$data = $this->processRequest();
$result = $this->resultJsonFactory->create();
$result->setData([
'success' => true,
'data' => $data,
]);
} catch (\Exception $e) {
$result = $this->resultJsonFactory->create();
$result->setData([
'success' => false,
'error' => $e->getMessage(),
]);
$result->setHttpResponseCode(400);
}
return $result;
}
JSON with CSL (Cross-Origin)
For AJAX endpoints that need to be called from different domains:
public function execute()
{
$result = $this->resultJsonFactory->create();
$result->setData(['data' => 'value']);
$result->setHeader('Access-Control-Allow-Origin', '*');
return $result;
}
Redirect Actions
Basic Redirect
<?php
namespace Amazon\Prep\Controller\Item;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\Result\RedirectFactory;
use Magento\Framework\Message\ManagerInterface;
class Delete implements HttpPostActionInterface
{
public function __construct(
private RedirectFactory $resultRedirectFactory,
private ManagerInterface $messageManager
) {}
public function execute()
{
$itemId = (int)$this->getRequest()->getParam('id');
try {
$this->deleteItem($itemId);
$this->messageManager->addSuccessMessage(__('Item deleted.'));
} catch (\Exception $e) {
$this->messageManager->errorMessage($e->getMessage());
}
return $this->resultRedirectFactory->create()
->setPath('amazon_prep/index/index');
}
}
Redirect with Parameters
// Redirect to specific item view
return $this->resultRedirectFactory->create()
->setPath('amazon_prep/item/view', ['id' => $itemId]);
// Redirect to external URL
return $this->resultRedirectFactory->create()
->setUrl('https://example.com');
// Redirect back to referring page
return $this->resultRedirectFactory->create()
->setUrl($this->_redirect->getRefererUrl());
Redirect with Data (Session)
public function execute()
{
$resultRedirect = $this->resultRedirectFactory->create();
try {
$this->saveItem();
$resultRedirect->setData(['success' => true]);
$this->messageManager->addSuccessMessage(__('Saved!'));
} catch (\Exception $e) {
$resultRedirect->setData(['error' => true]);
$this->messageManager->errorMessage($e->getMessage());
}
return $resultRedirect->setPath('amazon_prep/index/index');
}
Post-Redirect-Get Pattern
The PRG pattern prevents duplicate form submissions:
// SaveAction (POST)
public function execute()
{
$this->saveData();
$this->messageManager->addSuccessMessage(__('Saved!'));
return $this->resultRedirectFactory->create()
->setPath('amazon_prep/item/view', ['id' => $this->getItemId()]);
}
// ViewAction (GET) - displays success message from session
class View implements HttpGetActionInterface
{
public function execute(): Page
{
$page = $this->resultPageFactory->create();
// Message automatically displayed from session
return $page;
}
}
Forward (Internal)
use Magento\Framework\Controller\Result\ForwardFactory;
class Check implements HttpGetActionInterface
{
public function __construct(
private ForwardFactory $resultForwardFactory
) {}
public function execute()
{
if (!$this->isAuthorized()) {
$forward = $this->resultForwardFactory->create();
$forward->setController('index');
$forward->setAction('index');
$forward->forward('index');
return $forward;
}
// Continue processing
}
}
Complete CRUD Controller Set
Full CRUD Implementation
Product Controller Directory
Controller/Product/
├── Index.php (list)
├── View.php (detail)
├── NewAction.php (create form)
├── Edit.php (edit form)
├── Save.php (create/update)
└── Delete.php (remove)
IndexAction
namespace Amazon\Prep\Controller\Product;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Result\PageFactory;
class Index implements HttpGetActionInterface
{
public function __construct(private PageFactory $pageFactory) {}
public function execute()
{
$page = $this->pageFactory->create();
$page->getConfig()->setTitle(__('Products'));
return $page;
}
}
EditAction
namespace Amazon\Prep\Controller\Product;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Result\PageFactory;
use Magento\Framework\Controller\Result\RedirectFactory;
class Edit implements HttpGetActionInterface
{
public function __construct(
private PageFactory $pageFactory,
private RedirectFactory $redirectFactory
) {}
public function execute()
{
$id = (int)$this->getRequest()->getParam('id');
if (!$id) {
return $this->redirectFactory->create()
->setPath('amazon_prep/product/index');
}
$page = $this->pageFactory->create();
$page->getConfig()->setTitle(__('Edit Product'));
$page->getLayout()->getBlock('product.edit')
->setData('product_id', $id);
return $page;
}
}
SaveAction
namespace Amazon\Prep\Controller\Product;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\Result\RedirectFactory;
use Magento\Framework\Message\ManagerInterface;
use Amazon\Prep\Service\ProductService;
class Save implements HttpPostActionInterface
{
public function __construct(
private RedirectFactory $redirectFactory,
private ManagerInterface $messageManager,
private ProductService $productService
) {}
public function execute()
{
$data = $this->getRequest()->getParams();
$id = $data['id'] ?? null;
try {
$this->productService->save($data, $id);
$this->messageManager->addSuccessMessage(__('Product saved.'));
} catch (\Exception $e) {
$this->messageManager->errorMessage($e->getMessage());
}
return $this->redirectFactory->create()
->setPath('amazon_prep/product/index');
}
}
DeleteAction
namespace Amazon\Prep\Controller\Product;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\Result\RedirectFactory;
use Magento\Framework\Message\ManagerInterface;
class Delete implements HttpPostActionInterface
{
public function __construct(
private RedirectFactory $redirectFactory,
private ManagerInterface $messageManager
) {}
public function execute()
{
$id = (int)$this->getRequest()->getParam('id');
try {
$this->productService->delete($id);
$this->messageManager->addSuccessMessage(__('Product deleted.'));
} catch (\Exception $e) {
$this->messageManager->errorMessage($e->getMessage());
}
return $this->redirectFactory->create()
->setPath('amazon_prep/product/index');
}
}
Form Key Protection
Magento automatically validates form keys on POST requests. If you need to bypass for API:
public function execute()
{
if (!$this->_validateFormKey()) {
return $this->resultRedirectFactory->create()
->setPath('amazon_prep/product/index');
}
// Process form
}
Quiz
1. Which ResultFactory type should you use for a search endpoint that returns data?
2. What prevents duplicate form submissions in Magento?
3. How do you show a success message after a redirect?
Flashcards
Question
What does ResultFactory create?
Click to reveal answer
Answer
Response objects: Page (full page), Json (data), Redirect (HTTP 302), Forward (internal)
Question
How do you access request parameters?
Click to reveal answer
Answer
$this->getRequest()->getParam('key')
Question
What pattern prevents duplicate form submissions?
Click to reveal answer
Answer
Post-Redirect-Get (PRG)
Question
How do you add user feedback messages?
Click to reveal answer
Answer
$this->messageManager->addSuccessMessage() or errorMessage()
Question
What interface restricts actions to POST only?
Click to reveal answer
Answer
HttpPostActionInterface
Revision Notes
Key Takeaways
- 1. Use ResultFactory types: Page (HTML), Json (data), Redirect (URL change), Forward (internal)
- 2. Always implement HTTP method interfaces (HttpGetActionInterface, HttpPostActionInterface)
- 3. Use MessageManager for user feedback that persists across redirects
- 4. Follow PRG pattern for form submissions to prevent duplicates
- 5. Validate form keys on POST requests for CSRF protection
Interview Tips
- • Walk through creating a complete CRUD controller set
- • Explain the PRG pattern and why it matters
- • Know when to use Json vs Page result types
- • Be ready to discuss error handling in controllers
Cheat Sheet
Result types:
PageFactory → full page render
JsonFactory → JSON response
RedirectFactory → HTTP redirect
ForwardFactory → internal forward
HTTP methods:
implements HttpGetActionInterface
implements HttpPostActionInterface
Feedback:
$this->messageManager->addSuccessMessage(__('Done!'))
return $this->redirectFactory->create()->setPath('...');