Skip to content

Message Handlers

Complete documentation of all 36 MessageBus handlers that execute business logic in the Expensis automation system.


Overview

Metric Count
Total Handlers 36
Command Mappings 39 commands → 36 handlers
1:1 Mappings 35 perfect matches
Special Cases 1 (multi-command handler)

Each handler implements the MessageHandler interface and is decorated with #[AsMessageHandler].


Handler Architecture

sequenceDiagram
    participant Dispatcher
    participant MessageBus
    participant Router
    participant Handler
    participant DB
    participant Event

    Dispatcher->>MessageBus: dispatch(Command)
    MessageBus->>Router: Route to handler
    Router->>Handler: __invoke(Command)
    Handler->>DB: Execute business logic
    Handler->>Event: Trigger WorkerMessageHandledEvent
    Event->>Event: MessageConsumedHandler checks for chain
    alt Has chain
        Event->>MessageBus: Dispatch next command
    end

All Handlers (Alphabetical)

Sync & Import Handlers

Handler Command Purpose Location
ActivateRoutitAddonsHandler ActivateRoutitAddonsCommand Activate mobile addons via Routit Nina API src/MessageBus/AsynchronousHandler/
CancelRoutitAddonsHandler CancelRoutitAddonsCommand Cancel mobile addons via Routit Nina API src/MessageBus/AsynchronousHandler/
GrexxAsyncHandler GrexxAsyncCommand Sync Grexx orders, create chains src/MessageBus/AsynchronousHandler/
IrmaScraperAsyncHandler IrmaScraperAsyncCommand Scrape IRMA data src/MessageBus/AsynchronousHandler/
KpnEenPartnerSyncHandler KpnEenPartnerSyncCommand
SyncTaskEndStateCommand
KPN EEN partner sync (multi-command handler) src/MessageBus/AsynchronousHandler/
KpnEenSyncHandler KpnEenSyncCommand KPN EEN platform sync src/MessageBus/AsynchronousHandler/
KpnGripSyncHandler KpnGripSyncCommand KPN GRIP sync src/MessageBus/AsynchronousHandler/
KpnSp16ImportHandler KpnSp16ImportCommand Import KPN SP16 data src/MessageBus/AsynchronousHandler/
KpnSp16SyncHandler KpnSp16SyncCommand KPN SP16 sync orchestrator src/MessageBus/AsynchronousHandler/
PupeteerKpnSp16SyncHandler PupeteerKpnSp16SyncCommand KPN SP16 Puppeteer scraping src/MessageBus/AsynchronousHandler/
RoutitBillingSyncHandler RoutitBillingSyncCommand Routit billing sync src/MessageBus/AsynchronousHandler/
RoutitDownloadCdrsHandler RoutitDownloadCdrsCommand Download Routit CDR files src/MessageBus/AsynchronousHandler/
RoutitImportCdrsHandler RoutitImportCdrsCommand Import Routit CDRs (bulk insert) src/MessageBus/AsynchronousHandler/
RoutitInvoiceHandler RoutitInvoiceCommand Generate Routit invoice src/MessageBus/AsynchronousHandler/
RoutitRequestCdrsHandler RoutitRequestCdrsCommand Request CDRs from Routit API src/MessageBus/AsynchronousHandler/
RoutitSyncSubscriptionsHandler RoutitSyncSubscriptionsCommand Sync Routit subscriptions src/MessageBus/AsynchronousHandler/
TMobileCalviSyncHandler TMobileCalviSyncCommand T-Mobile Calvi sync src/MessageBus/AsynchronousHandler/
TMobileSyncHandler TMobileSyncCommand T-Mobile sync src/MessageBus/AsynchronousHandler/
VodafoneSyncHandler VodafoneSyncCommand Vodafone sync src/MessageBus/AsynchronousHandler/
YielderSyncHandler YielderSyncCommand Yielder/Odido sync src/MessageBus/AsynchronousHandler/

Calculation Handlers

Handler Command Purpose Location
BillingInsightTotalsHandler BillingInsightTotalsCommand Generate billing insights src/MessageBus/AsynchronousHandler/
CalculateSubscriptionTotalsHandler CalculateSubscriptionTotalsCommand Calculate subscription-level totals src/MessageBus/AsynchronousHandler/
CalculateTotalsHandler CalculateTotalsCommand Calculate customer billing totals src/MessageBus/AsynchronousHandler/
CalculateTotalsRateplanHandler CalculateTotalsRateplanCommand Calculate totals with rate plan logic src/MessageBus/AsynchronousHandler/
DispatchTotalsBackupHandler DispatchTotalsBackupCommand Backup totals to historical table src/MessageBus/AsynchronousHandler/
DispatchTotalsHandler DispatchTotalsCommand Route to correct totals calculation src/MessageBus/AsynchronousHandler/

Data Management Handlers

Handler Command Purpose Location
CalculationImportHandler CalculationImportCommand Import calculations from API src/MessageBus/AsynchronousHandler/
FillReportsHandler FillReportsCommand Generate and fill reports src/MessageBus/AsynchronousHandler/
GenerateCustomerExcelReportHandler GenerateCustomerExcelReportCommand Generate Excel export src/MessageBus/AsynchronousHandler/
GenerateKpnSP16InvoiceHandler GenerateKpnSP16InvoiceCommand Generate KPN SP16 invoice src/MessageBus/AsynchronousHandler/
FetchPdfKpnInvoiceHandler FetchPdfKpnInvoiceCommand Fetch KPN invoice PDF src/MessageBus/AsynchronousHandler/

Administrative Handlers

Handler Command Purpose Location
SaveStatusHandler SaveStatusCommand Update status records src/MessageBus/AsynchronousHandler/
SyncProfileHandler SyncProfileCommand Master router for all provider syncs src/MessageBus/AsynchronousHandler/
UpdateCostCenterHandler UpdateCostCenterCommand Update cost center data src/MessageBus/AsynchronousHandler/
UpdateParentOrderHandler UpdateParentOrderCommand Update parent order relationships src/MessageBus/AsynchronousHandler/

Critical Handlers

1. SyncProfileHandler (Master Router)

Purpose: Routes sync requests to provider-specific handlers

Location: src/MessageBus/AsynchronousHandler/SyncProfileHandler.php:153

Routing Logic:

public function __invoke(SyncProfileCommand $command) {
    $syncType = $command->getSyncProfile()->getSyncType();

    $syncCommand = match($syncType) {
        SyncType::KPN_SP16, SyncType::GRIP
            => new KpnSp16SyncCommand(...),
        SyncType::MY_VODAFONE
            => new VodafoneSyncCommand(...),
        SyncType::T_MOBILE
            => new TMobileCalviSyncCommand(...),
        SyncType::KPN_EEN
            => new KpnEenSyncCommand(...),
        SyncType::YIELDER
            => new YielderSyncCommand(...),
    };

    $this->commandBus->dispatch($syncCommand);
}

Impact: If this handler fails, ALL automated provider syncs stop

Usage: Dispatched every 5 minutes by sync:manager


2. DispatchTotalsHandler (Calculation Router)

Purpose: Routes totals calculation to appropriate handler

Location: src/MessageBus/AsynchronousHandler/DispatchTotalsHandler.php

Routing Logic:

public function __invoke(DispatchTotalsCommand $command) {
    if ($this->shouldUseRateplan($customer)) {
        $this->dispatch(new CalculateTotalsRateplanCommand(...));
    } else {
        $this->dispatch(new CalculateTotalsCommand(...));
    }
}

Impact: All billing calculation depends on this router

Usage: Dispatched after every provider sync


3. CalculateTotalsHandler

Purpose: Calculates billing totals from CDRs

Location: src/MessageBus/AsynchronousHandler/CalculateTotalsHandler.php

Process: 1. Query all CDRs for customer and billing cycle 2. Group by subscription and device 3. Apply rate plans 4. Calculate costs 5. Update totals table 6. Dispatch BillingInsightTotalsCommand

Impact: Core billing logic - incorrect calculations = incorrect bills

Performance: Handles 10k-50k CDRs per execution


Handlers That Create Chains

These handlers dispatch additional commands to create workflow chains:

Handler Dispatches To Chain Purpose
SyncProfileHandler Provider sync commands Route to specific provider
KpnSp16SyncHandler KpnSp16ImportCommand
PupeteerKpnSp16SyncCommand
DispatchTotalsCommand
Complete KPN SP16 workflow
DispatchTotalsHandler CalculateTotalsCommand OR
CalculateTotalsRateplanCommand
Route to correct calculation
CalculateTotalsHandler BillingInsightTotalsCommand Generate insights after calculation
RoutitInvoiceHandler CalculateTotalsCommand
BillingInsightTotalsCommand
SaveStatusCommand
Complete Routit billing chain
GrexxAsyncHandler UpdateParentOrderCommand
IrmaScraperAsyncCommand (optional)
Order processing chain
VodafoneSyncHandler DispatchTotalsCommand
DispatchTotalsBackupCommand
Vodafone complete flow
TMobileCalviSyncHandler DispatchTotalsCommand
DispatchTotalsBackupCommand
T-Mobile complete flow
YielderSyncHandler CalculateSubscriptionTotalsCommand
DispatchTotalsCommand
DispatchTotalsBackupCommand
Yielder complete flow

See: Workflows Documentation for complete chain traces


Handler Patterns

Standard Pattern

#[AsMessageHandler]
class ExampleHandler
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private MessageBusInterface $commandBus,
        private LoggerInterface $logger
    ) {}

    public function __invoke(ExampleCommand $command): void
    {
        try {
            // 1. Extract command data
            $customerId = $command->getCustomerId();

            // 2. Execute business logic
            $result = $this->doWork($customerId);

            // 3. Persist changes
            $this->entityManager->flush();

            // 4. Dispatch follow-up commands (if needed)
            if ($command->hasChain()) {
                foreach ($command->getChain() as $nextCommand) {
                    $this->commandBus->dispatch($nextCommand);
                }
            }
        } catch (\Exception $e) {
            $this->logger->error("Handler failed: {$e->getMessage()}");
            throw $e;
        }
    }
}

Router Pattern

#[AsMessageHandler]
class RouterHandler
{
    public function __invoke(RouterCommand $command): void
    {
        // Determine target handler
        $target = $this->determineTarget($command);

        // Create appropriate command
        $targetCommand = match($target) {
            'typeA' => new TypeACommand(...),
            'typeB' => new TypeBCommand(...),
            default => throw new \Exception("Unknown type")
        };

        // Forward with chain
        if ($command->hasChain()) {
            $targetCommand->withChain($command->getChain());
        }

        $this->commandBus->dispatch($targetCommand);
    }
}

Special Cases

Multi-Command Handler

KpnEenPartnerSyncHandler handles TWO commands: - KpnEenPartnerSyncCommand - SyncTaskEndStateCommand

Configuration: config/services.yaml

App\MessageBus\AsynchronousHandler\KpnEenPartnerSyncHandler:
    tags:
        - { name: messenger.message_handler, handles: App\MessageBus\Command\KpnEenPartnerSyncCommand }
        - { name: messenger.message_handler, handles: App\MessageBus\Command\SyncTaskEndStateCommand }

Why: Both commands perform similar sync finalization logic


Handler Complexity

Most Complex Handlers

Handler Lines of Code External APIs Database Tables Complexity
KpnSp16SyncHandler ~300 3 (KPN portals) 8 Very High
RoutitImportCdrsHandler ~250 File system 5 High
CalculateTotalsHandler ~200 None 6 High
VodafoneSyncHandler ~180 2 (Vodafone API) 7 High
TMobileCalviSyncHandler ~150 1 (Calvi API) 6 Medium

Simplest Handlers

Handler Lines of Code Purpose Complexity
SaveStatusHandler ~30 Update status field Very Low
DispatchTotalsBackupHandler ~40 Copy totals row Low
ActivateRoutitAddonsHandler ~50 Single API call Low

Finding Handlers

By Provider

KPN: - KpnSp16SyncHandler - KpnSp16ImportHandler - PupeteerKpnSp16SyncHandler - KpnGripSyncHandler - KpnEenSyncHandler - KpnEenPartnerSyncHandler - GenerateKpnSP16InvoiceHandler - FetchPdfKpnInvoiceHandler

T-Mobile: - TMobileCalviSyncHandler - TMobileSyncHandler

Vodafone: - VodafoneSyncHandler

Routit: - RoutitRequestCdrsHandler - RoutitDownloadCdrsHandler - RoutitImportCdrsHandler - RoutitInvoiceHandler - RoutitBillingSyncHandler - RoutitSyncSubscriptionsHandler - ActivateRoutitAddonsHandler - CancelRoutitAddonsHandler

Yielder/Odido: - YielderSyncHandler

Other: - GrexxAsyncHandler - IrmaScraperAsyncHandler

By Function

Sync Operations: All handlers ending in "SyncHandler"

Calculations: - CalculateTotalsHandler - CalculateTotalsRateplanHandler - CalculateSubscriptionTotalsHandler - BillingInsightTotalsHandler - DispatchTotalsHandler - DispatchTotalsBackupHandler

Data Import: - KpnSp16ImportHandler - RoutitImportCdrsHandler - RoutitDownloadCdrsHandler - CalculationImportHandler

Report Generation: - GenerateCustomerExcelReportHandler - GenerateKpnSP16InvoiceHandler - FillReportsHandler - FetchPdfKpnInvoiceHandler

Administrative: - SaveStatusHandler - UpdateCostCenterHandler - UpdateParentOrderHandler