Skip to content

feat: add Messenger class for multiple adapter failover support#115

Open
TorstenDittmann wants to merge 2 commits intomainfrom
feat-multiple-adapters-failover
Open

feat: add Messenger class for multiple adapter failover support#115
TorstenDittmann wants to merge 2 commits intomainfrom
feat-multiple-adapters-failover

Conversation

@TorstenDittmann
Copy link
Copy Markdown
Contributor

Summary

This PR introduces the Messenger class, which enables automatic failover across multiple messaging adapters. This addresses the feature request to allow using multiple adapters where if one fails, it falls back to the next one.

Changes

  • New Class: Utopia\Messaging\Messenger

    • Orchestrates multiple adapters for failover support
    • Accepts a single Adapter or an array of Adapter[]
    • Tries adapters sequentially when exceptions are thrown
    • Validates all adapters are compatible (same type and message type)
    • Returns the first successful response
    • Throws aggregated exception with detailed error messages if all adapters fail
  • New Tests: tests/Messaging/MessengerTest.php

    • 14 comprehensive test cases
    • Tests success, failover, and all-fail scenarios
    • Tests adapter validation and edge cases
  • Documentation: Updated README.md with usage example

Usage Example

Behavior

  • Failover trigger: Only on exceptions (not on returned failure payloads)
  • Validation: Ensures all adapters are of the same type (SMS, Email, Push)
  • Message type validation: Ensures all adapters support the same message type
  • Max messages: Returns the minimum maxMessagesPerRequest across all adapters

Testing

All tests pass:

  • ✅ 14 Messenger-specific tests
  • ✅ PSR-12 linting
  • ✅ PHPStan level 6 static analysis

Checklist

  • Code follows PSR-12 style guidelines
  • Static analysis passes (PHPStan level 6)
  • Tests added and passing
  • README updated with usage example
  • No breaking changes to existing adapters

This commit introduces the Messenger class, which enables automatic failover
across multiple messaging adapters. If one adapter throws an exception, the
next adapter in the sequence is tried until one succeeds or all fail.

Features:
- Accepts a single Adapter or an array of Adapters
- Tries adapters sequentially on exception
- Validates adapter compatibility (same type and message type)
- Returns the first successful response
- Throws aggregated exception with details if all adapters fail
- Supports SMS, Email, Push, and any other adapter types

Example usage:
    $messenger = new Messenger([
        new Twilio('sid', 'token'),
        new Vonage('key', 'secret'),
    ]);
    $result = $messenger->send($message);

Changes:
- Add src/Utopia/Messaging/Messenger.php
- Add tests/Messaging/MessengerTest.php with comprehensive test coverage
- Update README.md with usage example

Closes: feature request for multiple adapter support
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 2, 2026

Greptile Summary

This PR introduces the Messenger class, a clean orchestration layer that wraps one or more Adapter instances and provides automatic sequential failover when adapters throw exceptions. The implementation is well-structured and fits naturally into the existing Adapter / Message architecture.

Key highlights:

  • Constructor input validation is thorough: empty arrays, non-Adapter elements, and mixed adapter types/message types are all caught with descriptive \InvalidArgumentException messages
  • The failover loop correctly catches \Exception and accumulates error context before throwing an aggregated exception
  • validateAdapters correctly skips the self-comparison by using array_slice from index 1
  • getMaxMessagesPerRequest() returns the conservative minimum across all adapters
  • 14 test cases cover success, failover, all-fail, validation, and edge-case scenarios thoroughly

Two minor observations:

  • send() throws a generic \Exception for message-type mismatches while the constructor consistently uses \InvalidArgumentException for the same class of error (wrong argument)
  • getMaxMessagesPerRequest() docblock promises the value "ensures the messenger never accepts a message that any adapter cannot handle," but send() does not enforce this limit — a batch exceeding the minimum can silently succeed via a higher-capacity fallback adapter

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 style/consistency suggestions that do not affect correctness or reliability.

All previously flagged concerns have been addressed in this revision. The two remaining comments are minor: an inconsistent exception type in send() and an unenforced docblock promise on getMaxMessagesPerRequest(). Neither causes incorrect runtime behavior. The core failover logic, validation, and tests are solid.

src/Utopia/Messaging/Messenger.php — minor exception-type inconsistency and unenforced limit contract in send()

Important Files Changed

Filename Overview
src/Utopia/Messaging/Messenger.php New Messenger class implementing failover across multiple adapters. Well-structured with solid validation. Minor: send() throws \Exception for message type mismatch while the constructor consistently uses \InvalidArgumentException for argument errors.
tests/Messaging/MessengerTest.php 14 comprehensive test cases covering success, failover, all-fail, validation, and edge cases. Tests are thorough and well-structured.
README.md Added usage example for the new Messenger class under a new "Multiple Adapters (Failover)" section. Clear and accurate documentation.

Reviews (2): Last reviewed commit: "fix: harden Messenger validation and rev..." | Re-trigger Greptile

Validate Messenger adapter arrays at runtime so invalid elements fail with a clear InvalidArgumentException instead of a PHP error. This keeps the new Adapter|Adapter[] constructor ergonomic without weakening input validation.

Also replace Messenger sprintf-based error construction with direct string concatenation, pluralize the single-adapter failure message correctly, and add test coverage for single-adapter construction and invalid array elements.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant