gianfriaur / opcua-php-client-session-manager
Session manager daemon for persistent OPC UA connections in PHP — Unix socket IPC, automatic session recovery, subscription transfer
Package info
github.com/php-opcua/opcua-session-manager
pkg:composer/gianfriaur/opcua-php-client-session-manager
Requires
- php: ^8.2
- php-opcua/opcua-client: ^4.1
- psr/event-dispatcher: ^1.0
- psr/log: ^3.0
- psr/simple-cache: ^3.0
- react/event-loop: ^1.5
- react/socket: ^1.16
Requires (Dev)
- pestphp/pest: ^3.0
README
Keep OPC UA sessions alive across PHP requests. A daemon-based session manager for opcua-client that eliminates the 50–200ms connection handshake overhead on every HTTP request.
PHP's request/response model destroys all state — including network connections — at the end of every request. OPC UA requires a 5-step handshake (TCP → Hello/Ack → OpenSecureChannel → CreateSession → ActivateSession) that must be repeated every single time. This package solves the problem with a long-running ReactPHP daemon that holds sessions in memory, communicating with PHP applications via a lightweight Unix socket IPC protocol.
What you get:
- Session persistence — OPC UA connections survive across HTTP requests. Pay the handshake cost once, reuse forever
- Automatic session reuse — reconnecting to the same endpoint returns the existing session automatically, no manual session ID tracking needed
- Drop-in replacement —
ManagedClientimplements the sameOpcUaClientInterfaceas the directClient. Swap one line, keep all your code - All OPC UA operations — browse, read, write, method calls, subscriptions, history, path resolution, type discovery
- Security hardening — method whitelist, IPC authentication, credential stripping, error sanitization, connection limits
- Auto-publish — daemon automatically publishes for sessions with active subscriptions and dispatches PSR-14 events (
DataChangeReceived,AlarmActivated, etc.) — no manual publish loop needed - Auto-connect — daemon can auto-connect and register subscriptions at startup from pre-configured connection definitions
- Automatic cleanup — expired sessions are disconnected after configurable inactivity timeout
- Graceful shutdown — SIGTERM/SIGINT cleanly disconnect all active sessions
A note on versioning: We're aware of the rapid major releases in a short time frame. This library is under active, full-time development right now — the goal is to reach a production-stable state as quickly as possible. Breaking changes are being bundled and shipped deliberately to avoid dragging them out across many minor releases. Once the API surface settles, major version bumps will become rare. Thanks for your patience.
Tested against the OPC UA reference implementationThe underlying opcua-client is integration-tested against UA-.NETStandard — the reference implementation maintained by the OPC Foundation, the organization that defines the OPC UA specification. This is the same stack used by major industrial vendors to certify their products. This session manager is additionally integration-tested via uanetstandard-test-suite, verifying that all OPC UA operations work correctly when proxied through the daemon's IPC layer. |
Quick Start
composer require php-opcua/opcua-session-manager
1. Start the daemon
php bin/opcua-session-manager
2. Use ManagedClient in your PHP code
use PhpOpcua\SessionManager\Client\ManagedClient; $client = new ManagedClient(); $client->connect('opc.tcp://localhost:4840'); $value = $client->read('i=2259'); echo $value->getValue(); // 0 = Running $client->disconnect();
That's it. Same API as the direct Client, but the session stays alive between requests.
See It in Action
Session persistence across requests
// Request 1: open session — handshake happens once $client = new ManagedClient(); $client->connect('opc.tcp://localhost:4840'); // Do NOT call disconnect() — session stays alive in daemon // Request 2: same endpoint → reuses existing session automatically $client = new ManagedClient(); $client->connect('opc.tcp://localhost:4840'); $client->wasSessionReused(); // true — no handshake needed $value = $client->read('i=2259'); // ~5ms instead of ~155ms // If you need a separate parallel session to the same server: $client2 = new ManagedClient(); $client2->connectForceNew('opc.tcp://localhost:4840'); $client2->wasSessionReused(); // false — new session created
Browse and read
$refs = $client->browse('i=85'); foreach ($refs as $ref) { echo "{$ref->displayName} ({$ref->nodeId})\n"; } $nodeId = $client->resolveNodeId('/Objects/Server/ServerStatus'); $status = $client->read($nodeId);
Read multiple values with fluent builder
$results = $client->readMulti() ->node('i=2259')->value() ->node('ns=2;i=1001')->displayName() ->execute();
Write to a PLC
// Auto-detection (v4) — type inferred automatically $client->write('ns=2;i=1001', 42); // Explicit type (still supported) use PhpOpcua\Client\Types\BuiltinType; $client->write('ns=2;i=1001', 42, BuiltinType::Int32);
Subscribe to data changes
$sub = $client->createSubscription(publishingInterval: 500.0); $client->createMonitoredItems($sub->subscriptionId, [ ['nodeId' => 'ns=2;i=1001'], ]); $response = $client->publish(); foreach ($response->notifications as $notif) { echo $notif['dataValue']->getValue() . "\n"; }
Auto-publish (no manual publish loop)
When the daemon is started with an EventDispatcherInterface and autoPublish: true, it automatically calls publish() for sessions that have subscriptions. The client's PSR-14 events are dispatched to your listeners:
use PhpOpcua\Client\Event\DataChangeReceived; use PhpOpcua\Client\Event\AlarmActivated; use Psr\EventDispatcher\EventDispatcherInterface; // 1. Start daemon with auto-publish $daemon = new SessionManagerDaemon( socketPath: '/tmp/opcua.sock', clientEventDispatcher: $yourPsr14Dispatcher, autoPublish: true, ); // 2. Pre-configure connections to auto-connect on startup $daemon->autoConnect([ 'plc-1' => [ 'endpoint' => 'opc.tcp://192.168.1.10:4840', 'config' => [], 'subscriptions' => [ [ 'publishing_interval' => 500.0, 'max_keep_alive_count' => 5, 'monitored_items' => [ ['node_id' => 'ns=2;s=Temperature', 'client_handle' => 1], ['node_id' => 'ns=2;s=Pressure', 'client_handle' => 2], ], 'event_monitored_items' => [ ['node_id' => 'i=2253', 'client_handle' => 10], ], ], ], ], ]); $daemon->run(); // DataChangeReceived, EventNotificationReceived, AlarmActivated events // are dispatched to your PSR-14 listeners automatically.
Secure connection with authentication
use PhpOpcua\Client\Security\SecurityPolicy; use PhpOpcua\Client\Security\SecurityMode; $client = new ManagedClient( socketPath: '/var/run/opcua-session-manager.sock', authToken: trim(file_get_contents('/etc/opcua/daemon.token')), ); // RSA security $client->setSecurityPolicy(SecurityPolicy::Basic256Sha256); $client->setSecurityMode(SecurityMode::SignAndEncrypt); $client->setClientCertificate('/certs/client.pem', '/certs/client.key'); $client->setUserCredentials('operator', 'secret'); $client->connect('opc.tcp://192.168.1.100:4840');
// ECC security (auto-generated ECC certificate) $client = new ManagedClient(); $client->setSecurityPolicy(SecurityPolicy::EccNistP256); $client->setSecurityMode(SecurityMode::SignAndEncrypt); $client->setUserCredentials('operator', 'secret'); $client->connect('opc.tcp://192.168.1.100:4840');
Tip: Skip
setClientCertificate()and a self-signed cert gets auto-generated in memory (RSA for RSA policies, ECC for ECC policies) — perfect for quick tests or servers with auto-accept.
ECC disclaimer: ECC security policies (
EccNistP256,EccNistP384,EccBrainpoolP256r1,EccBrainpoolP384r1) are fully implemented and tested against the OPC Foundation's UA-.NETStandard reference stack. However, no commercial OPC UA vendor supports ECC endpoints yet.
How It Works
┌──────────────┐ ┌──────────────────────────────┐ ┌──────────────┐
│ PHP Request │ ──IPC──►│ Session Manager Daemon │ ──TCP──►│ OPC UA │
│ (short- │◄──IPC── │ │◄──TCP── │ Server │
│ lived) │ │ ● ReactPHP event loop │ │ │
└──────────────┘ │ ● Sessions in memory │ └──────────────┘
│ ● Periodic cleanup timer │
┌──────────────┐ │ ● Signal handlers │
│ PHP Request │ ──IPC──►│ │
│ (reuses │◄──IPC── │ Sessions: │
│ session) │ │ [sess-a1b2] → Client (TCP) │
└──────────────┘ │ [sess-c3d4] → Client (TCP) │
└──────────────────────────────┘
Without the session manager:
Request 1: [connect 150ms] [read 5ms] [disconnect] → total ~155ms
Request 2: [connect 150ms] [read 5ms] [disconnect] → total ~155ms
With the session manager:
Request 1: [open session 150ms] [read 5ms] → total ~155ms (first time only)
Request 2: [read 5ms] → total ~5ms
Request N: [read 5ms] → total ~5ms
Features
| Feature | What it does |
|---|---|
| Drop-in Replacement | ManagedClient implements the same OpcUaClientInterface as the direct Client |
| Session Persistence | OPC UA sessions survive across PHP requests via the daemon |
| Automatic Session Reuse | Reconnecting to the same endpoint returns the existing session instead of creating a new one |
| All OPC UA Operations | Browse, read, write, method calls, subscriptions, history, path resolution |
| String NodeIds | All methods accept 'i=2259' or 'ns=2;s=MyNode' in addition to NodeId objects |
| Fluent Builder API | readMulti(), writeMulti(), createMonitoredItems(), translateBrowsePaths() support chainable builders |
| Typed Returns | All service responses return public readonly DTOs — SubscriptionResult, CallResult, BrowseResultSet, etc. |
| Type Discovery | discoverDataTypes() auto-detects custom server structures |
| Transfer & Recovery | transferSubscriptions() and republish() for session migration |
| PSR-3 Logging | Optional structured logging via any PSR-3 logger |
| PSR-16 Cache | Cache management forwarded to daemon — invalidateCache(), flushCache() |
| Security | 10 policies (RSA + ECC), 3 auth modes, IPC authentication, method whitelist |
| Auto-Retry | Automatic reconnect on connection failures |
| Auto-Batching | Transparent batching for readMulti()/writeMulti() |
| Auto-Publish | Daemon automatically calls publish() for sessions with subscriptions and dispatches PSR-14 events |
| Auto-Connect | Daemon connects and registers subscriptions at startup from pre-configured definitions |
| Automatic Cleanup | Expired sessions closed after inactivity timeout |
| Graceful Shutdown | SIGTERM/SIGINT disconnect all sessions cleanly |
Daemon Options
php bin/opcua-session-manager [options]
| Option | Default | Description |
|---|---|---|
--socket <path> |
/tmp/opcua-session-manager.sock |
Unix socket path |
--timeout <sec> |
600 |
Session inactivity timeout |
--cleanup-interval <sec> |
30 |
Expired session cleanup interval |
--auth-token <token> |
(none) | Shared secret for IPC authentication |
--auth-token-file <path> |
(none) | Read auth token from file (recommended) |
--max-sessions <n> |
100 |
Maximum concurrent sessions |
--socket-mode <octal> |
0600 |
Socket file permissions |
--allowed-cert-dirs <dirs> |
(none) | Comma-separated allowed certificate directories |
Auth token priority: OPCUA_AUTH_TOKEN env var > --auth-token-file > --auth-token.
Security
The daemon implements multiple layers of security hardening:
- IPC authentication — shared-secret token validated with timing-safe
hash_equals() - Socket permissions —
0600by default (owner-only) - Method whitelist — only 45 documented OPC UA operations allowed via
query - Credential protection — passwords and private key paths stripped immediately after connection
- Session limits — configurable maximum to prevent resource exhaustion
- Certificate path restrictions —
--allowed-cert-dirsconstrains certificate directories - Input size limit — IPC requests capped at 1MB
- Connection protection — 30s per-connection timeout, max 50 concurrent IPC connections
- Error sanitization — messages truncated, file paths stripped
- PID file lock — prevents multiple daemon instances
Recommended production setup
openssl rand -hex 32 > /etc/opcua/daemon.token chmod 600 /etc/opcua/daemon.token OPCUA_AUTH_TOKEN=$(cat /etc/opcua/daemon.token) php bin/opcua-session-manager \ --socket /var/run/opcua-session-manager.sock \ --socket-mode 0660 \ --max-sessions 50 \ --allowed-cert-dirs /etc/opcua/certs
Comparison
Direct Client |
ManagedClient |
|
|---|---|---|
| Connection | Direct TCP | Via daemon (Unix socket) |
| Session lifetime | Dies with PHP process | Persists across requests |
| Per-operation overhead | ~1–5ms | ~5–15ms |
| Connection overhead | ~50–200ms every request | ~50–200ms first time only |
| Subscriptions | Lost between requests | Maintained by daemon |
| Certificate paths | Relative or absolute | Absolute only |
Documentation
| # | Document | Covers |
|---|---|---|
| 01 | Introduction | Overview, requirements, quick start |
| 02 | Overview & Architecture | Problem, solution, components |
| 03 | Installation | Requirements, Composer setup, project structure |
| 04 | Daemon | CLI options, security, systemd/Supervisor, internals |
| 05 | ManagedClient API | Full API reference, configuration, session persistence |
| 06 | IPC Protocol | Transport, commands, authentication, wire format |
| 07 | Type Serialization | JSON conversion for all OPC UA types and DTOs |
| 08 | Testing | Test infrastructure, helper class, running tests |
| 09 | Examples | Complete code examples for all features |
Testing
./vendor/bin/pest # everything ./vendor/bin/pest tests/Unit/ # unit only ./vendor/bin/pest tests/Integration/ --group=integration # integration only
456+ tests (unit + integration). Integration tests run against uanetstandard-test-suite — a Docker-based OPC UA environment built on the OPC Foundation's UA-.NETStandard reference implementation — covering browse, read/write, subscriptions, method calls, path resolution, connection state, security, type serialization, session persistence, session recovery, and all v4.0.0 DTOs.
Note on coverage:
SessionManagerDaemonis excluded from coverage reports because it runs as a separate long-lived process (ReactPHP event loop). PHP coverage tools (pcov, xdebug) only instrument the test runner process — they cannot track code executing inside a subprocess started viaproc_open(). The daemon is fully tested by the integration suite, which starts a real daemon, sends IPC commands, and verifies responses. This is a known limitation shared by other daemon-based PHP packages (Laravel Horizon, Symfony Messenger, RoadRunner workers).
Ecosystem
| Package | Description |
|---|---|
| opcua-client | Pure PHP OPC UA client |
| opcua-cli | CLI tool — browse, read, write, watch, discover endpoints, manage certificates, generate code from NodeSet2.xml |
| opcua-session-manager | Daemon-based session persistence across PHP requests (this package) |
| opcua-client-nodeset | Pre-generated PHP types from 51 OPC Foundation companion specifications (DI, Robotics, Machinery, MachineTool, ISA-95, CNC, MTConnect, and more). 807 PHP files — NodeId constants, enums, typed DTOs, codecs, registrars with automatic dependency resolution. Just composer require and loadGeneratedTypes(). |
| laravel-opcua | Laravel integration — service provider, facade, config |
| uanetstandard-test-suite | Docker-based OPC UA test servers (UA-.NETStandard) for integration testing |
AI-Ready
This package ships with machine-readable documentation designed for AI coding assistants (Claude, Cursor, Copilot, ChatGPT, and others). Feed these files to your AI so it knows how to use the library correctly:
| File | Purpose |
|---|---|
llms.txt |
Compact project summary — architecture, key classes, API signatures, and configuration. Optimized for LLM context windows with minimal token usage. |
llms-full.txt |
Comprehensive technical reference — every class, method, DTO, serialization format, IPC protocol, and daemon internal. For deep dives and complex questions. |
llms-skills.md |
Task-oriented recipes — step-by-step instructions for common tasks (install, configure, deploy, persist sessions, subscriptions, security, monitoring). Written so an AI can generate correct, production-ready code from a user's intent. |
How to use: copy the files you need into your project's AI configuration directory. The files are located in vendor/php-opcua/opcua-session-manager/ after composer install.
- Claude Code: reference per-session with
--add-file vendor/php-opcua/opcua-session-manager/llms-skills.md - Cursor: copy into your project's rules directory —
cp vendor/php-opcua/opcua-session-manager/llms-skills.md .cursor/rules/opcua-session-manager.md - GitHub Copilot: copy or append the content into your project's
.github/copilot-instructions.mdfile (create the file and directory if they don't exist). Copilot reads this file automatically for project-specific context - Other tools: paste the content into your system prompt, project knowledge base, or context configuration
Roadmap
See ROADMAP.md for what's coming next.
Contributing
Contributions welcome — see CONTRIBUTING.md.
Versioning
This package follows the same version numbering as php-opcua/opcua-client. Each release of opcua-session-manager is aligned with the corresponding release of the client library to ensure full compatibility.
Changelog
See CHANGELOG.md.