php-opcua/opcua-session-manager
Session manager daemon for persistent OPC UA connections in PHP โ Unix socket IPC, automatic session recovery, subscription transfer
Maintainers
Package info
github.com/php-opcua/opcua-session-manager
pkg:composer/php-opcua/opcua-session-manager
Requires
- php: ^8.2
- php-opcua/opcua-client: ^4.4.0
- 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
Suggests
None
Provides
None
Conflicts
None
Replaces
None
MIT 3d005e83e2d90ef62e7671a9a8fda11fa45bb3d2
- Gianfrancesco Aurecchia <gianfri.woop@php-opcua.com>
automationdaemonipcplciotsession-manageropcuaopc-uaindustrialscadaopc uapersistent-connection
README
๐ OPC UA Session Manager
๐ Tests
๐ Coverage
๐ Latest Version
๐ PHP Version
๐ License
๐ Linux
๐ macOS
๐ Windows
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 local IPC protocol (Unix-domain socket on Linux/macOS, TCP loopback on Windows โ auto-selected).
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
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 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. Like opcua-client, unit tests run cross-OS โ Linux, macOS, and Windows across PHP 8.2โ8.5 โ on every push. Integration tests stay on Linux (Docker-hosted OPC UA servers). |
Runs on Linux, macOS, and WindowsThe daemon and
Security posture is identical on every OS:
Named pipes on Windows were evaluated and intentionally skipped โ see the ROADMAP for the full cost/benefit analysis. |
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 |
| Third-party modules | Any method registered by a custom ServiceModule on the daemon is callable via ManagedClient::$method(...) โ typed args/results travel through a JSON wire codec with an explicit type allowlist |
| Cross-platform IPC | Auto-selects Unix-domain sockets on Linux/macOS and TCP loopback on Windows via TransportFactory. Endpoints accept unix://<path>, tcp://127.0.0.1:<port>, or a scheme-less Unix path (backwards-compatible). Loopback-only guard on both client and daemon sides |
| 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 <uri> |
per-OS (unix:///tmp/opcua-session-manager.sock on Linux/macOS, tcp://127.0.0.1:9990 on Windows) |
IPC endpoint URI. Accepts unix://<path>, tcp://127.0.0.1:<port>, or a scheme-less Unix path. TCP binds are loopback-only (construction-time guard refuses non-loopback hosts). |
--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 (applied only to Unix-socket endpoints) |
--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
Full documentation is available in docs/. Highlights:
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 module 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.
