sebastianbergmann/phpunit · error · EventAlreadyAssignedException

Event "%s" already assigned

Error message

Event "%s" already assigned

What it means

TypeMap enforces a strict one-to-one relation: each event class may be assigned to exactly one subscriber interface. ensureEventWasNotAlreadyAssigned() scans the mapping values with in_array($eventClass, $this->mapping, true) and throws EventAlreadyAssignedException when the event is already claimed by another (or the same) interface.

Source

Thrown at src/Event/TypeMap.php:182

        if (array_key_exists($subscriberInterface, $this->mapping)) {
            throw new SubscriberTypeAlreadyRegisteredException(
                sprintf(
                    'Subscriber type "%s" already registered',
                    $subscriberInterface,
                ),
            );
        }
    }

    /**
     * @param class-string $eventClass
     *
     * @throws EventAlreadyAssignedException
     */
    private function ensureEventWasNotAlreadyAssigned(string $eventClass): void
    {
        if (in_array($eventClass, $this->mapping, true)) {
            throw new EventAlreadyAssignedException(
                sprintf(
                    'Event "%s" already assigned',
                    $eventClass,
                ),
            );
        }
    }
}

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Register one subscriber interface per event and register multiple subscriber objects of that interface; the dispatcher stores a list of subscribers per event class.
  2. If the events genuinely differ, introduce a distinct event class (subclass or sibling implementing Event) for the second mapping.
  3. Drop or rename the conflicting mapping so each event class appears once.

Example fix

// before
$typeMap->addMapping(ExecutionFinishedSubscriber::class, ExecutionFinished::class);
$typeMap->addMapping(MetricsFlushSubscriber::class, ExecutionFinished::class); // already assigned

// after
$typeMap->addMapping(ExecutionFinishedSubscriber::class, ExecutionFinished::class);
$typeMap->addMapping(MetricsFlushSubscriber::class, MetricsFlushed::class); // distinct event
// or register two ExecutionFinishedSubscriber instances instead
Defensive patterns

Strategy: try-catch

Try / catch

try {
    $typeMap->addMapping($subscriberInterface, $eventClass);
} catch (PHPUnit\Event\EventAlreadyAssignedException $e) {
    // One event maps to one interface: register subscriber *instances* instead of a second interface
}

Prevention

When it happens

Trigger: addMapping(SecondSubscriberInterface::class, SameEvent::class) after (FirstSubscriberInterface, SameEvent) is registered; re-adding a built-in event class, since Facade::registerDefaultTypes() already claims every stock event class.

Common situations: Wanting two different subscriber types for the same event (the design answer is multiple subscriber instances of the one interface, not two interfaces); merging a custom map over the default map; refactoring one subscriber interface into several while keeping the event.

Related errors


AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23). Data as JSON: /api/errors/967761ff9c2857ff. Report an issue: GitHub.