Eine geschützte Mutation bauen: Veranstaltung als Favorit markieren (nur eingeloggte Kunden)
Eine geschützte Mutation bauen: Veranstaltung als Favorit markieren (nur eingeloggte Kunden)
~8 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026
Mit dem Wissen aus Kapitel 17 rüstet dieses Kapitel die naive AddEventToFavorites-Mutation aus Kapitel 16 zu einer wirklich geschützten Mutation aus - der letzte fehlende Baustein für das eigentliche Ziel des Veranstaltungen-Projekts.
Die Ressourcen-Klasse für Favoriten
Bevor der Resolver abgesichert wird, fehlt noch die konkrete Datenbankanbindung an mironsoft_event_customer_favorite (Kapitel 11), auf die IsFavorite (Kapitel 17) bereits referenziert:
<?php
declare(strict_types=1);
namespace Mironsoft\Event\Model\ResourceModel;
use Magento\Framework\App\ResourceConnection;
/**
* Direct low-level access to the mironsoft_event_customer_favorite table.
* A dedicated model/collection pair is intentionally skipped here - the
* linkage table has no identity of its own beyond the composite key.
*/
class EventFavorite
{
private const TABLE_NAME = 'mironsoft_event_customer_favorite';
/**
* @param ResourceConnection $resourceConnection Database connection provider
*/
public function __construct(
private readonly ResourceConnection $resourceConnection,
) {
}
/**
* Marks the given event as a favorite for the given customer (idempotent).
*
* @param int $customerId
* @param int $eventId
* @return void
*/
public function addFavorite(int $customerId, int $eventId): void
{
$connection = $this->resourceConnection->getConnection();
$connection->insertOnDuplicate(
$this->resourceConnection->getTableName(self::TABLE_NAME),
['event_id' => $eventId, 'customer_id' => $customerId]
);
}
/**
* Checks whether the given event is a favorite of the given customer.
*
* @param int $customerId
* @param int $eventId
* @return bool
*/
public function isFavorite(int $customerId, int $eventId): bool
{
$connection = $this->resourceConnection->getConnection();
$select = $connection->select()
->from($this->resourceConnection->getTableName(self::TABLE_NAME), ['event_id'])
->where('event_id = ?', $eventId)
->where('customer_id = ?', $customerId);
return $connection->fetchOne($select) !== false;
}
}Den Resolver absichern
<?php
declare(strict_types=1);
namespace Mironsoft\Event\Model\Resolver;
use Magento\Authorization\Model\UserContextInterface;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
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, restricted to logged-in
* customers.
*/
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
* @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>
* @throws GraphQlAuthorizationException
*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
?array $value = null,
?array $args = null
): array {
if ($context->getUserType() !== UserContextInterface::USER_TYPE_CUSTOMER
|| !$context->getExtensionAttributes()->getIsCustomer()
) {
throw new GraphQlAuthorizationException(
__('You must be logged in as a customer to favorite an event.')
);
}
$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,
],
];
}
}Die doppelte Prüfung - getUserType() UND getIsCustomer() - ist bewusst redundant: USER_TYPE_CUSTOMER allein schließt zwar Gäste und Admins aus, aber getIsCustomer() ist die von Magentos eigenen Customer-Resolvern genutzte, offiziell dokumentierte Prüfung. Beide zusammen sind auf der sicheren Seite, auch falls sich das Verhalten einer der beiden Methoden in einem zukünftigen Magento-Minor-Release ändert.
Den vollständigen Fluss testen
mutation {
addEventToFavorites(input: { event_id: 3 }) {
event {
title
is_favorite
}
}
}Mit gültigem Authorization: Bearer-Header liefert die Antwort is_favorite: true direkt in derselben Antwort - der Wert kommt aus dem IsFavorite-Resolver aus Kapitel 17, der auf demselben model-Schlüssel aufsetzt, den AddEventToFavorites hier im Rückgabearray mitliefert.
Achtung: Ohne Authorization-Header liefert dieselbe Mutation jetzt einen Fehler mit "category": "graphql-authorization" statt stillschweigend Kunde 0 zu befüllen - genau die Lücke aus Kapitel 16 ist damit geschlossen. Kapitel 19 vertieft, wie sich diese und weitere Fehlerarten sauber unterscheiden lassen.
Mit einer echten, geschützten Mutation ist der Schreibpfad des Projekts funktional komplett. Kapitel 19 rundet Block 5 mit einer systematischen Fehlerbehandlung über beide Resolver hinweg ab.