opcua-session-manager · v4.3.x
Docs · ManagedClient

Opening and closing

Four methods govern the lifecycle: connect, connectForceNew, disconnect, reconnect. They mean different things from their direct-client counterparts — read the differences before relying on the names.

The session lifecycle on ManagedClient is shaped by the daemon holding the OPC UA session. The client's connect(), disconnect(), reconnect() semantics are about the client-side relationship with the daemon, not the OPC UA session itself — which lives on the daemon's side independently.

connect()

Opens (or reuses) a daemon-side OPC UA session for $endpointUrl with the configuration currently set on $client.

php examples/connect.php
$client = new ManagedClient('/tmp/opcua-session-manager.sock');

$client
    ->setTimeout(10.0)
    ->setSecurityPolicy(SecurityPolicy::Basic256Sha256)
    ->setSecurityMode(SecurityMode::SignAndEncrypt)
    ->setClientCertificate('/etc/opcua/client.pem', '/etc/opcua/client.key');

$client->connect('opc.tcp://plc.local:4840');

What happens under the covers:

  1. 01

    Wire encode the configuration.

    Every set*() call accumulated into the client is serialised into a config map that is part of the open IPC command.

  2. 02

    Send the `open` command to the daemon.

    The daemon receives {method: "open", args: [endpointUrl, config]}.

  3. 03

    The daemon checks for an existing session match.

    Session keying is (endpointUrl, sanitized config) — see Session reuse. On match, the existing session ID is returned. On miss, the daemon opens a new OPC UA session and returns its ID.

  4. 04

    `$client->wasSessionReused()` and `getSessionId()` populate.

    The client now holds the daemon-assigned session ID. Subsequent IPC calls reference this ID to address the right session.

A successful connect() returns void. Failures raise:

  • DaemonException — IPC failure (socket unreachable, auth rejected, frame oversized).
  • ConnectionException — the daemon could not reach the OPC UA server (propagated from inside the daemon).
  • SecurityException — TLS / certificate failure on the OPC UA side.
  • ServiceException — server rejected the CreateSession / ActivateSession request.

connectForceNew()

Same shape as connect() but bypasses the session reuse check. The daemon opens a fresh OPC UA session unconditionally, even if a matching one already exists.

php examples/force-new.php
$client->connectForceNew('opc.tcp://plc.local:4840');

assert($client->wasSessionReused() === false);   // always false

Use it when:

  • You need a clean session state. A previous session has accumulated server-side state (subscriptions, monitored items) you want to discard.
  • You suspect the cached session is stale. Rare; the daemon detects most staleness via the attemptSessionRecovery() path internally, but escape hatches matter.
  • Per-call isolation. A particular operation wants to run on its own session so its failure does not cascade into shared state.

The cost is the full OPC UA handshake — exactly what the daemon is designed to amortise. Use connectForceNew() sparingly.

disconnect()

ManagedClient::disconnect() issues a close IPC command and tears the daemon-side session down:

Action Happens?
Sends {command: "close", sessionId: ...} to the daemon Yes — see ManagedClient::disconnect()
Daemon calls $session->client->disconnect() and removes the session Yes — see CommandHandler::handleClose()
OPC UA secure channel + session torn down on the server Yes — the underlying Client::disconnect() does this
Local IPC resources released YessessionId, describe cache, wire codec are cleared

The next call from any ManagedClient with the same (endpointUrl, sanitized config) tuple pays the OPC UA handshake again, because the session is gone from the store.

The OPC UA session on the daemon side is reclaimed when any of these happen:

  • Explicit close IPC command — what disconnect() does.
  • Inactivity timeout--timeout, default 600 seconds without any IPC activity referencing the session.
  • Daemon restart — all sessions terminate.

Pitfall: short-lived processes lose reuse. In a Laravel / CLI / FPM pattern where a request constructs a ManagedClient, uses it, and lets it go out of scope, the request ends with a close (either explicit or via the destructor pattern your code uses) and the daemon-side session disappears before the next request arrives — defeating the reuse the session manager exists to provide. To keep the daemon session alive across requests, reuse the ManagedClient object (singleton bind in Laravel, long-running worker, etc.) without calling disconnect() between calls.

reconnect()

Issues a query IPC command with method = "reconnect" against the current sessionId. The daemon delegates to the underlying IClient::reconnect(), which rebuilds the OPC UA secure channel without reopening a brand-new session.

What reconnect() does What it does not do
Sends {command:"query", sessionId, method:"reconnect"} Re-issue an open IPC command
Asks the daemon to rebuild the OPC UA channel for the existing session Open a new session ID
Preserves subscriptions tracked by the daemon Discard the session-store entry

If the current session is gone (daemon restart, expired), the daemon raises session_not_found and ManagedClient surfaces a ConnectionException("Session expired or not found: …"). In that case call connect($endpointUrl) (or connectForceNew()) to open a fresh session.

For the recovery pattern, see Recipes · Recovery and reconnect.

isConnected() and getConnectionState()

These methods reflect the daemon-side OPC UA session state, not the IPC connection. isConnected() returns true whenever the daemon reports ConnectionState::Connected for the session; getConnectionState() returns the full enum (Disconnected, Connected, Broken).

Both calls cost one IPC round-trip — they are not free. Cache them in the application if they appear in a hot path; the daemon does not push state changes to the client.

getSessionId()

Returns the opaque daemon-assigned session ID, or null if the client has never connected. Useful for logging, debugging, correlating with the daemon's list command output:

php examples/session-id.php
$client->connect('opc.tcp://plc.local:4840');
$logger->info('opcua.session.open', ['sessionId' => $client->getSessionId()]);

The ID is not the OPC UA AuthenticationToken or SessionId — it is the daemon's internal identifier, scoped to the daemon process. Two daemons running in parallel produce different IDs for the same OPC UA session.

Lifecycle in summary

text lifecycle
new ManagedClient(socket)         ← no IPC, no OPC UA

       │ set*(...)                ← still no IPC

       │ connect($url)            ← IPC open command

  Daemon checks session reuse:
     match    → return existing ID, wasSessionReused = true
     no match → open new OPC UA session, return its ID

       │ read / write / browse … ← per-call IPC round-trip

       │ disconnect()             ← sends `close` IPC; daemon tears
       │                            down the OPC UA session as well

new connect() on another ManagedClient must re-handshake. Session
reuse only kicks in when the previous client is still alive (it
has not called disconnect()) and another client opens with the
same sanitized config — see [Session reuse](./session-reuse.md).