Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Den Save-Controller richtig implementieren

Den Save-Controller richtig implementieren

~9 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026

Der Save-Controller ist das Gegenstück zum Form-DataProvider: Er nimmt POST-Daten entgegen, validiert sie (Kapitel 11), schreibt sie in die Datenbank und leitet je nach Ergebnis unterschiedlich weiter.

Vollständiger Save-Controller

app/code/Mironsoft/Announcement/Controller/Adminhtml/Announcement/Save.php
<?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.'));
        }
    }
}

HttpPostActionInterface nicht vergessen

Achtung: Ohne implements HttpPostActionInterface akzeptiert der Controller auch GET-Requests - das ist nicht nur unsauber, sondern seit den CSRF-Härtungsmaßnahmen des Magento-Admin auch ein potenzielles Sicherheitsproblem, da speichernde Aktionen zwingend an POST gebunden sein sollten.

DataPersistorInterface für Formular-Persistenz

Schlägt die Validierung fehl, gehen die eingegebenen Werte ohne DataPersistorInterface verloren - der Benutzer würde nach dem Redirect ein leeres Formular sehen und alles erneut eintippen müssen. Der Form-DataProvider muss den persistierten Wert seinerseits wieder einlesen (getData() in der DataProvider-Klasse prüft $this->dataPersistor->get() vor dem eigentlichen Collection-Load) - diese Ergänzung lohnt sich in jedem produktiven Formular.

"Speichern und weiter bearbeiten" berücksichtigen

Der Parameter back in der Redirect-Logik oben unterscheidet zwischen einem normalen Speichern (zurück zum Grid) und "Speichern und weiter bearbeiten" (zurück zum selben Formular) - dieser zweite Button wird in Kapitel 22 ergänzt, wenn eigene Toolbar-Buttons entstehen.