PHP Classes

File: doc/14-events.md

Recommend this page to a friend!
  Packages of Gianfrancesco Aurecchia   OPC UA Client   doc/14-events.md   Download  
File: doc/14-events.md
Role: Auxiliary data
Content type: text/markdown
Description: Auxiliary data
Class: OPC UA Client
Control devices that support the OPC UA protocol
Author: By
Last change:
Date: 5 days ago
Size: 11,390 bytes
 

Contents

Class file image Download

Events

Overview

The client dispatches granular PSR-14 events at every key lifecycle point. Inject any EventDispatcherInterface implementation ? Laravel's dispatcher, Symfony's event dispatcher, or your own ? and react to connections, sessions, subscriptions, data changes, alarms, reads, writes, browses, cache operations, and retries.

A NullEventDispatcher is used by default, ensuring zero overhead when no dispatcher is configured. Event objects are lazily instantiated via closures, so no allocation happens unless a real dispatcher is listening.

Configuration

use PhpOpcua\Client\ClientBuilder;
use Psr\EventDispatcher\EventDispatcherInterface;

$client = ClientBuilder::create()
    ->setEventDispatcher($yourDispatcher)
    ->connect('opc.tcp://localhost:4840');

// Get the current dispatcher (on the builder before connecting)
$builder = ClientBuilder::create();
$dispatcher = $builder->getEventDispatcher();

Laravel

$builder = ClientBuilder::create();
$builder->setEventDispatcher(app(EventDispatcherInterface::class));
$client = $builder->connect('opc.tcp://localhost:4840');

Or in a service provider:

$this->app->afterResolving(ClientBuilder::class, function (ClientBuilder $builder) {
    $builder->setEventDispatcher($this->app->make(EventDispatcherInterface::class));
});

Then listen with standard Laravel listeners:

// EventServiceProvider
protected $listen = [
    \PhpOpcua\Client\Event\DataChangeReceived::class => [
        \App\Listeners\HandleOpcUaDataChange::class,
    ],
    \PhpOpcua\Client\Event\AlarmActivated::class => [
        \App\Listeners\HandleOpcUaAlarm::class,
    ],
];

Event Reference

Every event is a readonly class in PhpOpcua\Client\Event\. All events carry a $client property referencing the OpcUaClientInterface that emitted them.

Connection Events

| Event | Properties | When | |---|---|---| | ClientConnecting | $endpointUrl | Before connect() starts | | ClientConnected | $endpointUrl | After successful connection | | ConnectionFailed | $endpointUrl, $exception | When connection attempt fails | | ClientReconnecting | $endpointUrl | Before reconnect() starts | | ClientDisconnecting | $endpointUrl | Before disconnect() starts | | ClientDisconnected | | After full disconnect |

Session Events

| Event | Properties | When | |---|---|---| | SessionCreated | $endpointUrl, $authenticationToken | After CreateSession succeeds | | SessionActivated | $endpointUrl | After ActivateSession succeeds | | SessionClosed | | Before session close request |

Secure Channel Events

| Event | Properties | When | |---|---|---| | SecureChannelOpened | $channelId, $securityPolicy, $securityMode | After secure channel is opened | | SecureChannelClosed | $channelId | Before secure channel close |

Subscription Events

| Event | Properties | When | |---|---|---| | SubscriptionCreated | $subscriptionId, $revisedPublishingInterval, $revisedLifetimeCount, $revisedMaxKeepAliveCount | After createSubscription() | | SubscriptionDeleted | $subscriptionId, $statusCode | After deleteSubscription() | | SubscriptionTransferred | $subscriptionId, $statusCode | After transferSubscriptions() (per item) | | MonitoredItemCreated | $subscriptionId, $monitoredItemId, $nodeId, $statusCode | After createMonitoredItems() / createEventMonitoredItem() (per item) | | MonitoredItemDeleted | $subscriptionId, $monitoredItemId, $statusCode | After deleteMonitoredItems() (per item) | | MonitoredItemModified | $subscriptionId, $monitoredItemId, $statusCode | After modifyMonitoredItems() (per item) | | TriggeringConfigured | $subscriptionId, $triggeringItemId, $addResults, $removeResults | After setTriggering() |

Publish Events

| Event | Properties | When | |---|---|---| | PublishResponseReceived | $subscriptionId, $sequenceNumber, $notificationCount, $moreNotifications | After every publish() call | | SubscriptionKeepAlive | $subscriptionId, $sequenceNumber | When publish() returns no notifications | | DataChangeReceived | $subscriptionId, $sequenceNumber, $clientHandle, $dataValue | Per data change notification | | EventNotificationReceived | $subscriptionId, $sequenceNumber, $clientHandle, $eventFields | Per event notification |

Alarm Events (Generic)

| Event | Properties | When | |---|---|---| | AlarmEventReceived | $subscriptionId, $clientHandle, $eventFields, $severity, $sourceName, $message, $eventType, $time | For every event notification with alarm-relevant data |

Alarm Events (Specific)

These are automatically deduced from event notification fields. They require the corresponding fields to be included in createEventMonitoredItem()'s $selectFields.

| Event | Properties | Deduced from | |---|---|---| | AlarmActivated | $subscriptionId, $clientHandle, $sourceName, $severity, $message | ActiveState = true / "Active" | | AlarmDeactivated | $subscriptionId, $clientHandle, $sourceName, $message | ActiveState = false / "Inactive" | | AlarmAcknowledged | $subscriptionId, $clientHandle, $sourceName | AckedState text contains "acknowledged" | | AlarmConfirmed | $subscriptionId, $clientHandle, $sourceName | ConfirmedState text contains "confirmed" | | AlarmShelved | $subscriptionId, $clientHandle, $sourceName | ShelvingState text contains "shelved" | | AlarmSeverityChanged | $subscriptionId, $clientHandle, $sourceName, $severity | Severity field present in notification | | LimitAlarmExceeded | $subscriptionId, $clientHandle, $sourceName, $limitState, $severity | EventType is a known LimitAlarm type | | OffNormalAlarmTriggered | $subscriptionId, $clientHandle, $sourceName, $severity | EventType is OffNormalAlarm/DiscreteAlarm |

Read / Write / Browse Events

| Event | Properties | When | |---|---|---| | NodeValueRead | $nodeId, $attributeId, $dataValue | After read() | | NodeValueWritten | $nodeId, $value, $type, $statusCode | After successful write() | | NodeValueWriteFailed | $nodeId, $statusCode | After write() with non-Good status | | NodeBrowsed | $nodeId, $direction, $resultCount | After browse() |

Write Type Detection Events

| Event | Properties | When | |---|---|---| | WriteTypeDetecting | $nodeId | Before type detection starts (read or cache lookup) | | WriteTypeDetected | $nodeId, $detectedType, $fromCache | After type is successfully determined |

Cache Events

| Event | Properties | When | |---|---|---| | CacheHit | $key | When a cached result is found | | CacheMiss | $key | When a cached result is not found |

Retry Events

| Event | Properties | When | |---|---|---| | RetryAttempt | $attempt, $maxRetries, $exception | Before each automatic retry | | RetryExhausted | $attempts, $exception | When all retries are exhausted |

Type Discovery Events

| Event | Properties | When | |---|---|---| | DataTypesDiscovered | $namespaceIndex, $count | After discoverDataTypes() completes |

Trust Store Events

| Event | Properties | When | |---|---|---| | ServerCertificateTrusted | $fingerprint, $subject | Server cert passes trust store validation | | ServerCertificateRejected | $fingerprint, $reason, $subject | Server cert rejected by trust store | | ServerCertificateAutoAccepted | $fingerprint, $subject | Server cert auto-accepted via TOFU | | ServerCertificateManuallyTrusted | $fingerprint, $subject | Cert added via trustCertificate() | | ServerCertificateRemoved | $fingerprint | Cert removed via untrustCertificate() |

Practical Examples

Log all data changes to a database

class DataChangeListener
{
    public function __invoke(DataChangeReceived $event): void
    {
        DB::table('opcua_values')->insert([
            'subscription_id' => $event->subscriptionId,
            'client_handle' => $event->clientHandle,
            'value' => $event->dataValue->getValue(),
            'status_code' => $event->dataValue->statusCode,
            'source_timestamp' => $event->dataValue->sourceTimestamp,
            'recorded_at' => now(),
        ]);
    }
}

Send Slack alerts on alarm activation

class AlarmAlertListener
{
    public function __invoke(AlarmActivated $event): void
    {
        Notification::route('slack', config('opcua.slack_webhook'))
            ->notify(new AlarmNotification(
                source: $event->sourceName,
                severity: $event->severity,
                message: $event->message,
            ));
    }
}

Monitor connection health

class ConnectionHealthListener
{
    public function handleConnected(ClientConnected $event): void
    {
        Cache::put("opcua:{$event->endpointUrl}:status", 'connected');
        Metrics::gauge('opcua.connections.active', 1);
    }

    public function handleFailed(ConnectionFailed $event): void
    {
        Cache::put("opcua:{$event->endpointUrl}:status", 'failed');
        Log::error('OPC UA connection failed', [
            'endpoint' => $event->endpointUrl,
            'error' => $event->exception->getMessage(),
        ]);
    }

    public function handleRetry(RetryAttempt $event): void
    {
        Metrics::increment('opcua.retries', tags: [
            'attempt' => $event->attempt,
        ]);
    }
}

Track subscription lifecycle for session manager

class SubscriptionTracker
{
    public function handleCreated(SubscriptionCreated $event): void
    {
        Redis::hSet('opcua:subscriptions', $event->subscriptionId, json_encode([
            'interval' => $event->revisedPublishingInterval,
            'created_at' => now()->toIso8601String(),
        ]));
    }

    public function handleDeleted(SubscriptionDeleted $event): void
    {
        Redis::hDel('opcua:subscriptions', $event->subscriptionId);
    }
}

Alarm event monitoring with extended fields

To receive specific alarm events (AlarmActivated, AlarmDeactivated, etc.), include the relevant state fields when creating the event monitored item:

$result = $client->createEventMonitoredItem(
    $sub->subscriptionId,
    $alarmNodeId,
    [
        'EventId', 'EventType', 'SourceName', 'Time', 'Message', 'Severity',
        'ActiveState',      // enables AlarmActivated / AlarmDeactivated
        'AckedState',       // enables AlarmAcknowledged
        'ConfirmedState',   // enables AlarmConfirmed
    ],
);

The default 6 fields (EventId, EventType, SourceName, Time, Message, Severity) always trigger AlarmEventReceived and AlarmSeverityChanged. Adding state fields beyond position 6 enables the corresponding specific events.

Performance

  • NullEventDispatcher (default): `dispatch()` does an `instanceof` check and returns immediately. No event object is allocated.
  • Lazy closures: all dispatch calls use `fn() => new Event(...)`. The closure is only invoked when a real dispatcher is set.
  • Zero overhead when unused: the entire event system adds no measurable cost to operations when no dispatcher is configured.