php-opcua / symfony-opcua
Symfony integration for OPC UA client with optional session manager support
Package info
github.com/php-opcua/symfony-opcua
Type:symfony-bundle
pkg:composer/php-opcua/symfony-opcua
Requires
- php: ^8.2
- php-opcua/opcua-client: ^4.4.0
- php-opcua/opcua-session-manager: ^4.4.0
- psr/event-dispatcher: ^1.0
- symfony/cache: ^7.4|^8.0
- symfony/config: ^7.4|^8.0
- symfony/console: ^7.4|^8.0
- symfony/dependency-injection: ^7.4|^8.0
- symfony/framework-bundle: ^7.4|^8.0
Requires (Dev)
- mockery/mockery: ^1.6
- pestphp/pest: ^3.0
- symfony/event-dispatcher: ^7.4|^8.0
- symfony/yaml: ^7.4|^8.0
This package is auto-updated.
Last update: 2026-06-09 16:17:05 UTC
README
Symfony integration for OPC UA built on opcua-client and opcua-session-manager. Connect your Symfony app to PLCs, SCADA systems, sensors, and IoT devices with a familiar developer experience: YAML-based semantic configuration, autowirable services, named connections, and a console command for the optional session manager daemon.
What you get:
- Dependency injection — autowire
OpcuaManagerorOpcUaClientInterfaceanywhere in your application - Named connections — define multiple OPC UA servers and switch between them, just like Doctrine connections
- Transparent session management — when the daemon is running, connections persist across HTTP requests; when it's not, direct per-request connections with zero code changes
- Symfony-native logging and caching — your Monolog channel and cache pool are automatically injected into every OPC UA client
- All OPC UA operations — browse, read, write, method calls, subscriptions, events, history, path resolution, type discovery
- PSR-14 events — 47 dispatched events covering every OPC UA operation for observability and extensibility
- Auto-publish — daemon monitors subscriptions automatically and dispatches PSR-14 events to your Symfony listeners — no manual publish loop
- Auto-connect — define subscriptions in YAML config per connection and the daemon sets them up at startup
- Trust store — certificate trust management with configurable policies and auto-accept modes
- Write auto-detection — omit the type parameter and let the client detect the correct OPC UA type automatically
Note: This bundle wraps the full opcua-client API with Symfony conventions. For the underlying protocol details, types, and advanced features, see the client documentation.
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 Symfony bundle is additionally integration-tested via uanetstandard-test-suite in both direct and managed (daemon) modes, ensuring full compatibility across all connection strategies. Like opcua-client and opcua-session-manager, unit tests run cross-OS — Linux, macOS, and Windows across PHP 8.2–8.5 × Symfony 7.4 / 8.0 — on every push. Integration tests stay on Linux (Docker-hosted OPC UA servers). |
Requirements
| Dependency | Version |
|---|---|
| PHP | >= 8.2 |
| ext-openssl | * |
| Symfony | 7.4, 8.0+ |
Symfony 7.3 and earlier: Symfony 7.3 reached end of life and no longer receives security fixes (PKSA-z7t6-zt6p-wtng), so the
composer.jsonfloor was raised to^7.4|^8.0and 7.3 was dropped from CI. Symfony 6.4 / 7.0 / 7.1 / 7.2 were never tested in CI either. These versions are unsupported; please upgrade to a maintained release.
Quick Start
composer require php-opcua/symfony-opcua
# config/packages/php_opcua_symfony_opcua.yaml php_opcua_symfony_opcua: connections: default: endpoint: '%env(OPCUA_ENDPOINT)%'
use PhpOpcua\SymfonyOpcua\OpcuaManager; class PlcController { public function __construct(private readonly OpcuaManager $opcua) {} public function index(): Response { $client = $this->opcua->connect(); $value = $client->read('i=2259'); echo $value->getValue(); // 0 = Running $client->disconnect(); } }
That's it. Autowire, YAML config, connect, read. Everything else is optional.
See It in Action
Browse the address space
$client = $this->opcua->connect(); $refs = $client->browse('i=85'); // Objects folder foreach ($refs as $ref) { echo "{$ref->displayName} ({$ref->nodeId})\n"; } $client->disconnect();
Read multiple values with fluent builder
$client = $this->opcua->connect(); $results = $client->readMulti() ->node('i=2259')->value() ->node('ns=2;i=1001')->displayName() ->execute(); foreach ($results as $dv) { echo $dv->getValue() . "\n"; } $client->disconnect();
Write to a PLC
use PhpOpcua\Client\Types\BuiltinType; $client = $this->opcua->connect(); $client->write('ns=2;i=1001', 42, BuiltinType::Int32); // Or let the client auto-detect the type $client->write('ns=2;i=1001', 42); $client->disconnect();
Call a method on the server
use PhpOpcua\Client\Types\Variant; use PhpOpcua\Client\Types\BuiltinType; $client = $this->opcua->connect(); $result = $client->call( 'i=2253', // Server object 'i=11492', // Method [new Variant(BuiltinType::UInt32, 1)], ); echo $result->statusCode; // 0 echo $result->outputArguments[0]->value; // [1001, 1002, ...] $client->disconnect();
Subscribe to data changes
$client = $this->opcua->connect(); $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"; } $client->deleteSubscription($sub->subscriptionId); $client->disconnect();
Auto-publish — no manual publish loop
With auto_publish enabled, the daemon handles subscriptions automatically. Just register Symfony event listeners:
# config/packages/php_opcua_symfony_opcua.yaml php_opcua_symfony_opcua: session_manager: auto_publish: true connections: plc-1: endpoint: 'opc.tcp://192.168.1.10:4840' auto_connect: true subscriptions: - publishing_interval: 500.0 monitored_items: - { node_id: 'ns=2;s=Temperature', client_handle: 1 }
use PhpOpcua\Client\Event\DataChangeReceived; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; #[AsEventListener] class StoreSensorReading { public function __invoke(DataChangeReceived $e): void { // Store $e->dataValue->getValue() with $e->clientHandle } }
php bin/console opcua:session # connects, subscribes, publishes — all automatic
Switch connections
// Named connection from config $client = $this->opcua->connect('plc-line-1'); $value = $client->read('ns=2;i=1001'); $this->opcua->disconnect('plc-line-1'); // Ad-hoc connection at runtime $client = $this->opcua->connectTo('opc.tcp://10.0.0.50:4840', [ 'username' => 'operator', 'password' => 'secret', ], as: 'temp-plc'); $this->opcua->disconnectAll();
Inject the default connection directly
use PhpOpcua\Client\OpcUaClientInterface; class PlcService { public function __construct(private readonly OpcUaClientInterface $client) {} public function readServerState(): int { return $this->client->read('i=2259')->getValue(); } }
Test without a real server
use PhpOpcua\Client\Testing\MockClient; use PhpOpcua\Client\Types\DataValue; $mock = MockClient::create() ->onRead('i=2259', fn() => DataValue::ofInt32(0)); // Inject into OpcuaManager via DI or reflection $value = $mock->read('i=2259'); echo $value->getValue(); // 0 echo $mock->callCount('read'); // 1
Features
| Feature | What it does |
|---|---|
| Autowiring | Inject OpcuaManager or OpcUaClientInterface (default connection) anywhere via constructor injection |
| Named Connections | Define multiple servers in YAML config, switch with $opcua->connection('plc-2') |
| Ad-hoc Connections | $opcua->connectTo('opc.tcp://...') for endpoints not in config |
| Session Manager | Console command php bin/console opcua:session for daemon-based session persistence |
| Transparent Fallback | Daemon available? ManagedClient. Not available? Direct Client. Zero code changes |
| String NodeIds | 'i=2259', 'ns=2;s=MyNode' everywhere a NodeId is accepted |
| Fluent Builder API | readMulti(), writeMulti(), createMonitoredItems(), translateBrowsePaths() chain |
| PSR-3 Logging | Monolog channel injected automatically via log_channel config |
| PSR-14 Events | 47 dispatched events covering every OPC UA operation for observability and extensibility |
| PSR-16 Caching | Symfony cache pool wrapped as PSR-16 and injected automatically. Per-call useCache on browse ops |
| Write Auto-Detection | write('ns=2;i=1001', 42) — omit the type and the client detects it automatically |
| Read Metadata Cache | Cached node metadata avoids redundant reads; refresh on demand with read($nodeId, refresh: true) |
| Trust Store | FileTrustStore with configurable TrustPolicy, auto-accept modes, and certificate management |
| Type Discovery | discoverDataTypes() auto-detects custom server structures |
| Auto-Publish | Daemon auto-publishes for sessions with subscriptions, dispatches PSR-14 events to Symfony listeners |
| Auto-Connect | Per-connection auto_connect with declarative subscriptions config — daemon sets up monitoring on startup |
| Subscription Management | createMonitoredItems(), modifyMonitoredItems(), setTriggering(), transferSubscriptions() |
| MockClient | Test without a server — register handlers, assert calls |
| Timeout & Retry | Per-connection timeout, auto_retry via YAML config |
| Auto-Batching | readMulti/writeMulti transparently split when exceeding server limits |
| Recursive Browse | browseAll(), browseRecursive() with depth control and cycle detection |
| Path Resolution | resolveNodeId('/Objects/Server/ServerStatus') |
| Security | 10 policies (RSA + ECC), 3 auth modes, auto-generated certs, certificate trust management |
| History Read | Raw, processed, and at-time historical queries |
| Typed Returns | All service responses return public readonly DTOs |
ECC disclaimer: ECC security policies (
ECC_nistP256,ECC_nistP384,ECC_brainpoolP256r1,ECC_brainpoolP384r1) are fully implemented and tested against the OPC Foundation's UA-.NETStandard reference stack. However, no commercial OPC UA vendor supports ECC endpoints yet. When using ECC, client certificates are auto-generated ifclient_certificate/client_keyare omitted, and username/password authentication uses theEccEncryptedSecretprotocol automatically.
Documentation
Full documentation is available in docs/. Highlights:
| Section | Covers |
|---|---|
| Getting started — Overview · Installation · Quick start · How symfony-opcua fits · Upgrading | Concepts, install, first connection |
| Configuration — Bundle YAML · Connections · Environment variables · Security · Session manager · Parameters & overrides | YAML, env, named connections |
| Using the client — Manager vs interface · Named connections · Ad-hoc connections · Connection lifecycle · Using builders | Manager service, DI, lifecycle |
| Operations — Reading · Writing · Browsing · Method calls · Subscriptions · History | Read/write, browse, subscribe, history |
| Session manager — Overview · Starting the daemon · Auto-publish · Production supervisor · Monitoring | Persistent sessions via daemon |
| Events — Overview · Connection events · Data events · Alarm events · Async listeners with Messenger | PSR-14 + Symfony EventDispatcher |
| Observability — Logging · Caching · Debugging · Profiler & data collectors | Logs, cache, profiler |
| Security — Policies & modes · Credentials · Certificates · Trust store | Security policies, certs, trust |
| Testing — PHPUnit & Pest setup · Mocking the manager · Using MockClient · Kernel tests | Unit + kernel tests |
| Integrations — Messenger · Mercure · Doctrine · Twig · Console & scheduler · Notifier | Symfony ecosystem |
| Reference — OpcuaManager API · Bundle services · Console commands · Exceptions | Public API |
| Recipes — Persistent tag history · Alarm routing · Mercure dashboard · Multi-plant tenant · Companion specs · Dev with Docker · Production deployment | Task-oriented walkthroughs |
Testing
144+ unit tests with 99%+ code coverage. Integration tests run against uanetstandard-test-suite — a Docker-based OPC UA environment built on the OPC Foundation's UA-.NETStandard reference implementation — in both direct and managed (daemon) modes.
./vendor/bin/pest tests/Unit/ # unit only ./vendor/bin/pest tests/Integration/ --group=integration # integration only ./vendor/bin/pest # everything
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. Keeps OPC UA connections alive between short-lived PHP processes via a ReactPHP daemon and Unix sockets. |
| 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. |
| laravel-opcua | Laravel integration — service provider, facade, config |
| symfony-opcua | Symfony integration — bundle, autowiring, YAML config (this package) |
| uanetstandard-test-suite | Docker-based OPC UA test servers (UA-.NETStandard) for integration testing |
Community
Have questions, ideas, or want to share what you've built? Join the GitHub Discussions.
Connected a PLC, SCADA system, or OPC UA server? We're building a community-driven list of tested hardware and software. Share your experience in Tested Hardware & Software — even a one-liner like "Siemens S7-1500, works fine" helps other users know what to expect.
Contributing
Contributions welcome — see CONTRIBUTING.md.
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, autowiring, configuration, session manager. Optimized for LLM context windows with minimal token usage. |
llms-full.txt |
Comprehensive technical reference — every config key, method, DTO, event, trust store, managed client. For deep dives and complex questions. |
llms-skills.md |
Task-oriented recipes — step-by-step instructions for common tasks (install, read, write, browse, named connections, session manager, security, testing, events). 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/symfony-opcua/ after composer install.
- Claude Code: reference per-session with
--add-file vendor/php-opcua/symfony-opcua/llms-skills.md - Cursor: copy into your project's rules directory —
cp vendor/php-opcua/symfony-opcua/llms-skills.md .cursor/rules/symfony-opcua.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
Versioning
This package follows the same version numbering as php-opcua/opcua-client. Each release of symfony-opcua is aligned with the corresponding release of the client library to ensure full compatibility.
Changelog
See CHANGELOG.md.