Overview
The session manager — a long-lived PHP daemon that holds OPC UA sessions on behalf of short-lived PHP processes. What it is, what it solves, when you need one, when you don't.
opcua-session-manager is a long-running PHP process that holds
OPC UA sessions in memory. Other PHP processes (your Laravel
app, queue workers, Octane requests) talk to it over a local
IPC channel instead of opening sessions directly.
Architecture
┌──────────────────────────────────────────────────────────┐
│ Laravel application │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ FPM req 1 │ │ FPM req 2 │ │ queue worker │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ │ │
│ unix socket / tcp │
└───────────────────────────┼──────────────────────────────┘
│
┌───────────────▼────────────────┐
│ OPC UA Session Manager │
│ ┌────────┐ ┌────────┐ │
│ │session1│ │session2│ ... │ long-lived
│ └───┬────┘ └───┬────┘ │
└─────────┼──────────┼───────────┘
│ │
┌───────▼─┐ ┌───▼────────┐
│ PLC #1 │ │ PLC #2 │
└─────────┘ └────────────┘
What it solves
1 — Session reuse across requests
OPC UA session establishment is expensive — handshake, key
exchange under Sign/SignAndEncrypt, server-side allocation.
A typical secured connection takes 200-500 ms to open. In a
PHP-FPM app where every request opens fresh, latency adds up
fast.
The daemon opens once, reuses many. Two FPM requests targeting the same PLC share one server-side session.
2 — Subscriptions that outlive requests
A Subscription in direct mode lives only as long as the PHP
process that created it. FPM requests are seconds. Subscriptions
need hours.
The daemon owns the subscription. Requests come and go; the daemon polls publish responses continuously.
3 — One server-side identity per (endpoint, identity) tuple
OPC UA servers often cap concurrent sessions per client identity. With direct mode, each FPM worker uses its own session — 50 workers ≈ 50 sessions, breaching the cap.
Daemon-held sessions are pooled. 50 workers, 1 daemon, 1 server- side session (per endpoint+identity).
When to use it
| Use case | Direct mode | Managed mode |
|---|---|---|
| Single artisan command, one-shot read | ✓ | overkill |
| Scheduled job, every 5 minutes | ✓ | overkill |
| FPM endpoint that reads occasionally | ✓ | optional |
| FPM endpoint, dozens of OPC UA calls per request | optional | recommended |
| Real-time subscription feeding a UI | hard | required |
| Production app with 5+ FPM workers | optional | recommended |
| Multi-tenant with 100+ connections | hard | recommended |
| Server-side session limit < worker count | breaks | required |
The rule of thumb: direct mode for scripts, managed mode for applications.
When NOT to use it
- Single-process tools where the OPC UA session needs to die with the process.
- Environments where you can't run a sidecar (some PaaS).
- Air-gapped tests where the additional moving part isn't worth it.
How the Laravel package picks the mode
At OpcuaManager::connection() time:
- Is
session_manager.enabledtrue in config? If not → direct mode. - Is the daemon reachable on
socket_path? Quick probe. If not → direct mode, log a warning. - Otherwise → managed mode.
Probe results are cached for a short time, so a slow daemon doesn't impose per-call cost.
You can force a mode:
OPCUA_SESSION_MANAGER_ENABLED=false # force direct
There is no per-connection session-manager toggle — the
session_manager.enabled flag is global. If you need one
connection to bypass the daemon while others use it, run a
second config or use Opcua::connectTo() from code with the
daemon disabled. (Earlier drafts mentioned a per-connection
session_manager_enabled key — the manager doesn't read it.)
ManagedClient — the wrapper
In managed mode, the package returns ManagedClient instead of
OpcuaClient. They implement the same OpcUaClientInterface,
so application code doesn't care.
What ManagedClient does internally:
- Talks to the daemon over IPC.
- Each call (
read,write, …) gets serialised to a JSON envelope, sent to the daemon, decoded on the other side. - The daemon dispatches to its in-memory session.
The wire format is documented under IPC · Envelope and framing.
The daemon's lifecycle
The daemon is independent of Laravel. It's a PHP CLI process that:
- Listens on a Unix socket or TCP port.
- Holds OPC UA sessions.
- Optionally publishes subscription events (auto-publish).
You launch it with php artisan opcua:session (Laravel-wired) or
vendor/bin/opcua-session-manager (raw CLI). In production, run
it under Supervisor / systemd — see
Production supervisor.
Per-deployment topology
A typical production topology:
| Component | Process count | Notes |
|---|---|---|
| Laravel FPM | 8-32 workers | Talk to the daemon |
| Laravel queue workers | 4-16 workers | Talk to the daemon |
| Octane / FrankenPHP | 4-16 workers | Talk to the daemon |
| OPC UA session daemon | 1 | Single tenant, holds all sessions |
| Horizon / Reverb | 1 each | Optional, broadcast subscription data |
Single daemon, many clients.
Multi-tenant deployments
For a hard-isolation multi-tenant deployment, run one daemon per tenant, each on its own socket. The package supports multiple socket paths by config — see Recipes · Multi-plant tenant.
For shared deployments where tenant isolation is at the OPC UA identity layer (different username/cert per tenant connection), one daemon is sufficient.
Where to read next
- Starting the daemon — the artisan command and its options.
- Auto-publish — the subscription event bridge.
- Production supervisor — systemd, Supervisor, Horizon process orchestration.