sebastianbergmann/phpunit · error · SubscriberTypeAlreadyRegisteredException
Subscriber type "%s" already registered
Error message
Subscriber type "%s" already registered
What it means
TypeMap refuses double registration: addMapping() throws SubscriberTypeAlreadyRegisteredException when the subscriber interface is already a key in the private mapping array. The map is append-only through the public API, so this usually means seeding logic ran twice or custom mappings were re-added on top of the Facade's defaults.
Source
Thrown at src/Event/TypeMap.php:165
if (!in_array(Event::class, class_implements($eventClass), true)) {
throw new InvalidEventException(
sprintf(
'Event "%s" does not implement Event interface',
$eventClass,
),
);
}
}
/**
* @param class-string $subscriberInterface
*
* @throws SubscriberTypeAlreadyRegisteredException
*/
private function ensureSubscriberWasNotAlreadyRegistered(string $subscriberInterface): void
{
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(View on GitHub (pinned to f123cdb2a2)
Solutions
- Track which interfaces you have registered (e.g. a static set keyed by interface name) and skip duplicates before calling addMapping().
- Deduplicate the configuration driving the registration loop so each interface appears once.
- Do not re-add built-in subscriber interfaces; Facade::registerDefaultTypes() already claims them.
Example fix
// before
foreach ($config->subscriberMappings() as $interface => $event) {
$typeMap->addMapping($interface, $event); // second pass throws
}
// after
$registered = [];
foreach ($config->subscriberMappings() as $interface => $event) {
if (isset($registered[$interface])) { continue; }
$typeMap->addMapping($interface, $event);
$registered[$interface] = true;
} Defensive patterns
Strategy: try-catch
Try / catch
try {
$typeMap->addMapping($subscriberInterface, $eventClass);
} catch (PHPUnit\Event\SubscriberTypeAlreadyRegisteredException $e) {
// Idempotent bootstrap: the interface is already mapped, nothing to do
} Prevention
- Make registration code idempotent: track already-registered interface names in a set and skip them.
- Deduplicate extension configuration before looping over mappings.
- Never re-add built-in subscriber interfaces on top of the Facade defaults.
When it happens
Trigger: Calling addMapping(MyInterface::class, ...) twice on the same TypeMap; a custom runner that merges its own mappings over a map already populated by Facade::registerDefaultTypes(); a registration loop iterating the same configuration twice.
Common situations: Custom bootstrap code that registers mappings once per loaded extension but is executed for multiple test suites in one process; copy-pasted registration blocks; duplicated extension entries in phpunit.xml.
Related errors
- Event "%s" already assigned
- Subscriber "%s" does not implement a known interface
- Subscriber "%s" does not exist or is not an interface
- Event class "%s" does not exist
- Subscriber "%s" does not extend Subscriber interface
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/49e74bf979a10b8c.
Report an issue: GitHub.