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

Was sind Mutations? Eine erste eigene Mutation schreiben

Was sind Mutations? Eine erste eigene Mutation schreiben

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

Alle bisherigen Kapitel haben ausschließlich gelesen. GraphQL trennt lesende und schreibende Operationen bewusst über zwei getrennte Basistypen: Query und Mutation. Dieses Kapitel baut die erste eigene Mutation - eine Veranstaltung als Favorit markieren - noch bewusst ohne Zugriffsschutz, um die reine Mutation-Mechanik zu zeigen. Die Kapitel 17 und 18 härten sie danach ab.

Query vs. Mutation: mehr als nur Benennung

Technisch könnte ein schreibender Vorgang auch über eine Query laufen - GraphQL erzwingt die Trennung nicht auf Protokollebene. Sie existiert trotzdem aus gutem Grund: Bei mehreren Top-Level-Feldern in einer einzigen Anfrage garantiert die Spezifikation für Mutation-Felder eine sequenzielle Ausführung (eine nach der anderen), während Query-Felder parallel ausgeführt werden dürfen. Wer mehrere Schreibvorgänge in einer Anfrage bündelt und sich auf eine bestimmte Reihenfolge verlässt, braucht dafür zwingend Mutation.

Das Input/Output-Muster

Magentos eigene Mutations - generateCustomerToken, addProductsToCart, createCustomer - folgen fast durchgängig demselben Muster: ein einziges input-Argument als Input-Typ (Kapitel 6) statt vieler loser Argumente, und ein eigener Output-Typ als Rückgabewert statt eines nackten Skalars oder Entity-Typs. Dieses Muster übernimmt auch die Favoriten-Mutation:

app/code/Mironsoft/Event/etc/schema.graphqls
type Mutation {
    addEventToFavorites(
        input: AddEventToFavoritesInput!
    ): AddEventToFavoritesOutput
        @resolver(class: "Mironsoft\\Event\\Model\\Resolver\\AddEventToFavorites")
        @doc(description: "Adds an event to the current customer's favorites")
}

input AddEventToFavoritesInput @doc(description: "Input for addEventToFavorites") {
    event_id: Int!
}

type AddEventToFavoritesOutput @doc(description: "Result of addEventToFavorites") {
    event: Event
}

Der Output-Typ gibt die aktualisierte Veranstaltung direkt zurück, statt nur "true"/"false" - so kann der Client in einer einzigen Anfrage sowohl die Mutation ausführen als auch die neuen Daten (inklusive des in Kapitel 17 ergänzten is_favorite-Feldes) abfragen, statt danach eine zweite Query nachzureichen.

Den Resolver schreiben (vorläufige Fassung)

app/code/Mironsoft/Event/Model/Resolver/AddEventToFavorites.php
<?php

declare(strict_types=1);

namespace Mironsoft\Event\Model\Resolver;

use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Mironsoft\Event\Api\EventRepositoryInterface;
use Mironsoft\Event\Model\ResourceModel\EventFavorite;

/**
 * Resolves the addEventToFavorites mutation field.
 *
 * NOTE: this first version blindly trusts the caller's customer ID - it is
 * hardened with a proper authentication check in chapter 18.
 */
class AddEventToFavorites implements ResolverInterface
{
    /**
     * @param EventRepositoryInterface $eventRepository Service contract for event access
     * @param EventFavorite $eventFavoriteResource Resource model for the favorites linkage table
     */
    public function __construct(
        private readonly EventRepositoryInterface $eventRepository,
        private readonly EventFavorite $eventFavoriteResource,
    ) {
    }

    /**
     * Adds the given event to the current customer's favorites.
     *
     * @param Field $field Resolved GraphQL field configuration
     * @param mixed $context Resolver context, carries the customer ID (chapter 17)
     * @param ResolveInfo $info GraphQL resolve tree info
     * @param array|null $value Parent resolver's value, unused for a top-level field
     * @param array|null $args Arguments passed to the addEventToFavorites field
     * @return array<string, mixed>
     */
    public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        ?array $value = null,
        ?array $args = null
    ): array {
        $eventId = (int) ($args['input']['event_id'] ?? 0);
        $customerId = (int) $context->getUserId();

        $event = $this->eventRepository->getById($eventId);
        $this->eventFavoriteResource->addFavorite($customerId, $eventId);

        return [
            'event' => [
                'event_id' => $event->getEventId(),
                'identifier' => $event->getIdentifier(),
                'title' => $event->getTitle(),
                'description' => $event->getDescription(),
                'location' => $event->getLocation(),
                'start_at' => $event->getStartAt(),
                'end_at' => $event->getEndAt(),
                'capacity' => $event->getCapacity(),
                'model' => $event,
            ],
        ];
    }
}

Achtung: $customerId = (int) $context->getUserId(); ist an dieser Stelle unsicher: Für einen Gast liefert getUserId() 0 oder null, und der Aufruf würde die Veranstaltung stillschweigend für Kunde 0 speichern, statt die Anfrage abzulehnen. Diese Lücke ist in diesem Kapitel bewusst noch offen - Kapitel 17 erklärt zunächst, wie Authentifizierung im Resolver-Context überhaupt funktioniert, bevor Kapitel 18 den fehlenden Schutz konkret nachrüstet.

Die Mutation mit Variablen testen

mutation AddFavorite($eventId: Int!) {
  addEventToFavorites(input: { event_id: $eventId }) {
    event {
      title
    }
  }
}

Tipp: Mutation-Argumente per Variablen zu übergeben (Kapitel 3) ist bei Mutations noch wichtiger als bei Queries - der Query-String einer Mutation landet oft in Server-Logs, und Werte direkt im String zu verketten erschwert die Wiederverwendung des immer gleichen Mutation-Strings über verschiedene Aufrufe hinweg.

Mit einer funktionierenden, aber noch ungeschützten Mutation geht es in Kapitel 17 an das Fundament jeder Zugriffskontrolle in GraphQL: Kunden-Token und der Customer-Context im Resolver.