Implementing the Save Controller Correctly
Implementing the Save Controller Correctly
~9 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
The save controller is the counterpart to the form DataProvider: it accepts POST data, validates it (chapter 11), writes it to the database, and redirects differently depending on the outcome.
The complete save controller
<?php
declare(strict_types=1);
namespace Mironsoft\Announcement\Controller\Adminhtml\Announcement;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Mironsoft\Announcement\Model\AnnouncementFactory;
use Mironsoft\Announcement\Model\ResourceModel\Announcement as AnnouncementResource;
/**
* Persists a created or edited announcement.
*/
class Save extends Action implements HttpPostActionInterface
{
public const ADMIN_RESOURCE = 'Mironsoft_Announcement::announcement';
private const PERSIST_KEY = 'mironsoft_announcement';
/**
* @param Context $context Backend action context.
* @param AnnouncementFactory $announcementFactory Factory for the announcement model.
* @param AnnouncementResource $resource Resource model for direct load/save.
* @param DataPersistorInterface $dataPersistor Keeps posted data on validation errors.
*/
public function __construct(
Context $context,
private readonly AnnouncementFactory $announcementFactory,
private readonly AnnouncementResource $resource,
private readonly DataPersistorInterface $dataPersistor,
) {
parent::__construct($context);
}
/**
* Validates and persists the posted announcement data.
*
* @return ResultInterface
*/
public function execute(): ResultInterface
{
$resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
$data = $this->getRequest()->getPostValue();
if (!$data) {
return $resultRedirect->setPath('mironsoft_announcement/announcement/index');
}
$id = (int) ($data['announcement_id'] ?? 0);
try {
$this->validate($data);
$announcement = $this->announcementFactory->create();
if ($id) {
$this->resource->load($announcement, $id);
}
$announcement->setData($data);
$this->resource->save($announcement);
$this->messageManager->addSuccessMessage(__('You saved the announcement.'));
$this->dataPersistor->clear(self::PERSIST_KEY);
if ($this->getRequest()->getParam('back')) {
return $resultRedirect->setPath(
'mironsoft_announcement/announcement/edit',
['id' => $announcement->getId()]
);
}
return $resultRedirect->setPath('mironsoft_announcement/announcement/index');
} catch (LocalizedException $exception) {
$this->messageManager->addErrorMessage($exception->getMessage());
} catch (\Throwable $exception) {
$this->messageManager->addErrorMessage(
__('Something went wrong while saving the announcement.')
);
}
$this->dataPersistor->set(self::PERSIST_KEY, $data);
return $resultRedirect->setPath(
'mironsoft_announcement/announcement/edit',
['id' => $id]
);
}
/**
* Validates the posted announcement data.
*
* @param array<string, mixed> $data Raw POST data.
* @return void
* @throws LocalizedException
*/
private function validate(array $data): void
{
if (trim((string) ($data['title'] ?? '')) === '') {
throw new LocalizedException(__('Title is required.'));
}
}
}Don't forget HttpPostActionInterface
Achtung: Without implements HttpPostActionInterface, the controller also accepts GET requests - that's not just untidy, it's also a potential security problem given the Magento admin's CSRF hardening, since actions that persist data must strictly be bound to POST.
DataPersistorInterface for form persistence
If validation fails, the entered values are lost without DataPersistorInterface - the user would see a blank form after the redirect and have to type everything again. The form DataProvider, in turn, has to read the persisted value back (getData() in the DataProvider class checks $this->dataPersistor->get() before the actual collection load) - this addition is worth adding to every production form.
Accounting for "save and continue editing"
The back parameter in the redirect logic above distinguishes between a plain save (back to the grid) and "save and continue editing" (back to the same form) - that second button gets added in chapter 22, when custom toolbar buttons are built.