Skip to content
beginner Phase 23 · Module Setup

Creating Controller Actions

Practical guide to creating Magento 2 controller actions: IndexAction, ResultFactory usage, page/JSON/redirect results, and form handling

45m
0 problems
Topic Progress 0%

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?

Question 1 options

2. What prevents duplicate form submissions in Magento?

Question 2 options

3. How do you show a success message after a redirect?

Question 3 options

Flashcards

Question

What does ResultFactory create?

Answer

Response objects: Page (full page), Json (data), Redirect (HTTP 302), Forward (internal)

Question

How do you access request parameters?

Answer

$this->getRequest()->getParam('key')

Question

What pattern prevents duplicate form submissions?

Answer

Post-Redirect-Get (PRG)

Question

How do you add user feedback messages?

Answer

$this->messageManager->addSuccessMessage() or errorMessage()

Question

What interface restricts actions to POST only?

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('...');