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

Delete-Controller und Bestätigungsdialog implementieren

Delete-Controller und Bestätigungsdialog implementieren

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

Kapitel 8 hat bereits MassDelete für mehrere ausgewählte Zeilen gebaut. Für das Löschen einer einzelnen Zeile direkt aus der Actions-Spalte braucht es einen eigenen Delete-Controller plus einen clientseitigen Bestätigungsdialog, der ohne eigenes JavaScript auskommt - Magento_Ui bringt beides bereits mit.

Die generische Actions-Spaltenklasse aus Kapitel 6 erzeugt standardmäßig nur einen Edit-Link. Ein eigener Column-Renderer ergänzt den Delete-Link mit Bestätigungsdialog - das volle Custom-Renderer-Muster vertieft Kapitel 19; hier vorab die konkrete Actions-Klasse für Kundenstimmen:

app/code/Mironsoft/Testimonial/Ui/Component/Listing/Column/TestimonialActions.php
<?php

declare(strict_types=1);

namespace Mironsoft\Testimonial\Ui\Component\Listing\Column;

use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;

/**
 * Renders edit and delete links for each testimonial grid row.
 */
class TestimonialActions extends Column
{
    private const URL_PATH_EDIT = 'mironsoft_testimonial/testimonial/edit';
    private const URL_PATH_DELETE = 'mironsoft_testimonial/testimonial/delete';

    /**
     * @param ContextInterface $context Rendering context.
     * @param UiComponentFactory $uiComponentFactory Factory for nested UI components.
     * @param UrlInterface $urlBuilder Builds admin URLs for the row actions.
     * @param array<string, mixed> $components Nested UI components.
     * @param array<string, mixed> $data Additional UI Component data configuration.
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        private readonly UrlInterface $urlBuilder,
        array $components = [],
        array $data = [],
    ) {
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Adds edit/delete URLs to every grid row of this column.
     *
     * @param array<string, mixed> $dataSource Raw grid data source.
     * @return array<string, mixed>
     */
    public function prepareDataSource(array $dataSource): array
    {
        if (!isset($dataSource['data']['items'])) {
            return $dataSource;
        }

        foreach ($dataSource['data']['items'] as &$item) {
            $itemId = (int) $item['testimonial_id'];

            $item[$this->getData('name')]['edit'] = [
                'href' => $this->urlBuilder->getUrl(self::URL_PATH_EDIT, ['id' => $itemId]),
                'label' => __('Edit'),
            ];
            $item[$this->getData('name')]['delete'] = [
                'href' => $this->urlBuilder->getUrl(self::URL_PATH_DELETE, ['id' => $itemId]),
                'label' => __('Delete'),
                'confirm' => [
                    'title' => __('Delete Testimonial'),
                    'message' => __('Are you sure you want to delete testimonial "%1"?', $item['customer_name']),
                ],
            ];
        }

        return $dataSource;
    }
}

Die confirm-Konfiguration im Array reicht bereits vollständig aus, um clientseitig Magento_Ui/js/modal/confirm auszulösen, bevor der Delete-Link tatsächlich aufgerufen wird - kein eigenes JavaScript nötig.

<actionsColumn name="actions" class="Mironsoft\Testimonial\Ui\Component\Listing\Column\TestimonialActions">
    <settings>
        <indexField>testimonial_id</indexField>
        <resizeEnabled>false</resizeEnabled>
        <sortable>false</sortable>
    </settings>
</actionsColumn>

Delete-Controller

app/code/Mironsoft/Testimonial/Controller/Adminhtml/Testimonial/Delete.php
<?php

declare(strict_types=1);

namespace Mironsoft\Testimonial\Controller\Adminhtml\Testimonial;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Mironsoft\Testimonial\Api\TestimonialRepositoryInterface;

/**
 * Deletes a single testimonial identified via the id request parameter.
 */
class Delete extends Action
{
    public const ADMIN_RESOURCE = 'Mironsoft_Testimonial::delete';

    /**
     * @param Context $context Backend action context.
     * @param TestimonialRepositoryInterface $testimonialRepository Loads/deletes testimonials.
     */
    public function __construct(
        Context $context,
        private readonly TestimonialRepositoryInterface $testimonialRepository,
    ) {
        parent::__construct($context);
    }

    /**
     * Deletes the requested testimonial and redirects back to the grid.
     *
     * @return ResultInterface
     */
    public function execute(): ResultInterface
    {
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        $id = (int) $this->getRequest()->getParam('id');

        if ($id) {
            try {
                $testimonial = $this->testimonialRepository->getById($id);
                $this->testimonialRepository->delete($testimonial);
                $this->messageManager->addSuccessMessage(__('You deleted the testimonial.'));
            } catch (NoSuchEntityException $exception) {
                $this->messageManager->addErrorMessage(__('This testimonial no longer exists.'));
            }
        }

        return $resultRedirect->setPath('mironsoft_testimonial/testimonial/index');
    }
}

delete() muss dafür noch auf TestimonialRepositoryInterface ergänzt werden - eine dünne Methode, die intern $this->resource->delete($testimonial) aufruft, analog zu save() aus Kapitel 16.

Auffällig: ADMIN_RESOURCE zeigt hier bewusst auf Mironsoft_Testimonial::delete, nicht auf den allgemeinen ::testimonial-Knoten aus Kapitel 14 - diese Trennung wird in Kapitel 25 offiziell in der ACL nachgezogen.

Tipp: Bei einer einzelnen Zeile führt der Bestätigungsdialog zu genau einem GET-Request auf die Delete-URL nach Bestätigung - eine unbeabsichtigte Massenlöschung durch Doppelklick ist damit praktisch ausgeschlossen, solange der Dialog nicht versehentlich weggelassen wird.