If you've ever tried to connect a PHP application to industrial machinery — a PLC, a SCADA system, a historian — you've probably hit a wall. The OPC UA standard is the lingua franca of industrial IoT, but most implementations assume you're writing in C++, Python, or .NET. PHP? Good luck finding anything that isn't a thin HTTP wrapper around a gateway you have to run separately.
That changes with php-opcua/opcua-client.
What Is OPC UA?
OPC UA (Unified Architecture) is an industrial communication standard developed by the OPC Foundation. It lets software clients talk to sensors, PLCs, SCADA systems, historians, and IoT devices over a standardized protocol — with built-in security, data typing, discovery, and more. It's the backbone of Industry 4.0 and IIoT architectures worldwide.
The protocol runs over TCP (binary encoding) and is notoriously complex to implement: it includes asymmetric and symmetric encryption, certificate management, session handling, subscriptions, historical queries, and a rich address space model.
Why a Pure PHP Implementation?
Most PHP projects that need OPC UA resort to one of:
- An HTTP REST gateway (adds a sidecar process, latency, and a point of failure)
- A COM bridge on Windows (commercial, not portable)
- Shelling out to a Python or Node.js script
php-opcua/opcua-client is different. It implements the entire OPC UA binary protocol stack natively in PHP, with only ext-openssl as a runtime dependency. No FFI. No COM. No gateway. Just composer require and you're connecting to PLCs from your Laravel app.
Quick Start
composer require php-opcua/opcua-client
use PhpOpcua\Client\ClientBuilder;
$client = ClientBuilder::create()
->connect('opc.tcp://localhost:4840');
$status = $client->read('i=2259');
echo $status->getValue(); // 0 = Running
$client->disconnect();
Three lines. No config files, no XML, no service containers.
NodeId strings like
'i=2259','ns=2;i=1001', or'ns=2;s=MyNode'are accepted everywhere. Invalid strings throwInvalidNodeIdException.
Core Features in Practice
Browse the Address Space
$refs = $client->browse('i=85'); // Objects folder
foreach ($refs as $ref) {
echo "{$ref->displayName} ({$ref->nodeId})\n";
// => Server (ns=0;i=2253)
// => MyPLC (ns=2;i=1000)
}
Read Multiple Values in One Round-Trip
$results = $client->readMulti()
->node('i=2259')->value()
->node('ns=2;i=1001')->displayName()
->node('ns=2;s=Temperature')->value()
->execute();
foreach ($results as $dataValue) {
echo $dataValue->getValue() . "\n";
}
The fluent builder auto-batches requests transparently — one TCP round-trip regardless of how many nodes you chain.
Write to a PLC
use PhpOpcua\Client\Types\BuiltinType;
// Auto-detect type (reads the node first, caches the result)
$client->write('ns=2;i=1001', 42);
// Explicit type
$client->write('ns=2;i=1001', 42, BuiltinType::Int32);
Subscribe to Real-Time Data Changes
$sub = $client->createSubscription(publishingInterval: 500.0);
$client->createMonitoredItems($sub->subscriptionId, [
['nodeId' => NodeId::numeric(2, 1001)],
]);
$response = $client->publish();
foreach ($response->notifications as $notif) {
echo $notif['dataValue']->getValue() . "\n";
}
Call Methods on the Server
use PhpOpcua\Client\Types\Variant;
$result = $client->call(
'i=2253', // Server object
'i=11492', // GetMonitoredItems method
[new Variant(BuiltinType::UInt32, 1)],
);
echo $result->statusCode; // 0 (Good)
print_r($result->outputArguments[0]->value); // [1001, 1002, ...]
Query Historical Data
$values = $client->historyReadRaw(
'ns=2;i=1001',
startTime: new DateTimeImmutable('-1 hour'),
endTime: new DateTimeImmutable(),
);
foreach ($values as $dv) {
echo "[{$dv->sourceTimestamp->format('H:i:s')}] {$dv->getValue()}\n";
}
Enterprise-Grade Security
The library supports 6 security policies, from None up to Aes256Sha256RsaPss, and three authentication modes: anonymous, username/password, and X.509 certificates.
use PhpOpcua\Client\Security\SecurityPolicy;
use PhpOpcua\Client\Security\SecurityMode;
$client = ClientBuilder::create()
->setSecurityPolicy(SecurityPolicy::Basic256Sha256)
->setSecurityMode(SecurityMode::SignAndEncrypt)
->setClientCertificate('/certs/client.pem', '/certs/client.key', '/certs/ca.pem')
->setUserCredentials('operator', 'secret')
->connect('opc.tcp://192.168.1.100:4840');
Omit
setClientCertificate()and a self-signed cert is auto-generated in memory — perfect for development or servers with auto-accept enabled.
The library also ships a persistent trust store with Trust-On-First-Use (TOFU) support:
use PhpOpcua\Client\TrustStore\FileTrustStore;
use PhpOpcua\Client\TrustStore\TrustPolicy;
$client = ClientBuilder::create()
->setTrustStore(new FileTrustStore()) // ~/.opcua/trusted/
->setTrustPolicy(TrustPolicy::Fingerprint)
->connect('opc.tcp://192.168.1.100:4840');
Laravel Integration
If you're on Laravel, there's a dedicated package:
composer require php-opcua/laravel-opcua
It ships a service provider, a facade, and config scaffolding so you can inject OpcUaClient from the container and configure connections in config/opcua.php. Fully compatible with Laravel's PSR-3 logger and PSR-14 event dispatcher.
47 PSR-14 Events — Zero Overhead When Unused
The library fires granular events for every lifecycle moment: connection, session, data change, alarms, retries, cache hits, and more. Plug in any PSR-14 dispatcher to react to them:
use PhpOpcua\Client\Event\AlarmActivated;
class AlarmHandler {
public function handleActivated(AlarmActivated $event): void {
Log::critical("Alarm: {$event->sourceName} (severity: {$event->severity})");
}
}
Without a dispatcher, the default NullEventDispatcher ensures zero overhead.
Testing Without a Real PLC
The library ships a MockClient that implements the same interface as the real client — no TCP connection required:
use PhpOpcua\Client\Testing\MockClient;
use PhpOpcua\Client\Types\DataValue;
$client = MockClient::create();
$client->onRead(function (NodeId $nodeId) {
return DataValue::ofDouble(23.5);
});
$value = $client->read('ns=2;s=Temperature');
echo $value->getValue(); // 23.5
echo $client->callCount('read'); // 1
Register handlers for onRead(), onWrite(), onBrowse(), onCall(), and onResolveNodeId(). Track calls with getCalls(), callCount(), and resetCalls(). Your CI pipeline never needs a real OPC UA server.
The CLI Tool
A companion CLI package lets you explore OPC UA servers from the terminal:
composer require php-opcua/opcua-cli
# Browse the address space
opcua-cli browse opc.tcp://192.168.1.10:4840 /Objects
# Read a value
opcua-cli read opc.tcp://192.168.1.10:4840 "ns=2;i=1001"
# Watch a value in real time
opcua-cli watch opc.tcp://192.168.1.10:4840 "ns=2;i=1001"
# Discover endpoints
opcua-cli endpoints opc.tcp://192.168.1.10:4840
# Manage trusted server certificates
opcua-cli trust opc.tcp://server:4840
It also generates PHP type files from NodeSet2.xml companion specifications — useful when working with vendor-specific data models.
Pre-Built Companion Types
The ecosystem includes php-opcua/opcua-client-nodeset, which ships pre-generated PHP types for 51 OPC Foundation companion specifications — Robotics, Machinery, MachineTool, ISA-95, CNC, MTConnect, and more:
use PhpOpcua\Nodeset\Robotics\RoboticsRegistrar;
use PhpOpcua\Nodeset\Robotics\Enums\OperationalModeEnumeration;
$client = ClientBuilder::create()
->loadGeneratedTypes(new RoboticsRegistrar())
->connect('opc.tcp://192.168.1.100:4840');
// Enum values auto-cast to PHP BackedEnum — not raw ints
$mode = $client->read(RoboticsNodeIds::OperationalMode)->getValue();
// OperationalModeEnumeration::MANUAL_REDUCED_SPEED
Structured types return typed DTOs with IDE autocomplete — no more digging through arrays.
What About Long-Lived Connections?
PHP's request/response model doesn't naturally support persistent TCP connections. For continuous monitoring or subscription polling, the ecosystem provides php-opcua/opcua-session-manager — a ReactPHP daemon that keeps OPC UA sessions alive across short-lived PHP requests via Unix sockets. It's a separate package by design: bundling a ReactPHP daemon would break the zero-dependency philosophy of the core library.
Feature Summary
| Feature | Status |
|---|---|
| Browse / Path resolution | ✅ |
| Read / Write (single & batch) | ✅ |
| Subscriptions & data change events | ✅ |
| Historical data (raw, processed, at-time) | ✅ |
| Method calls | ✅ |
| 6 security policies (None → Aes256Sha256RsaPss) | ✅ |
| Anonymous / Username / X.509 auth | ✅ |
| PSR-3 logging, PSR-14 events, PSR-16 cache | ✅ |
| MockClient for testing | ✅ |
| Laravel service provider + facade | ✅ |
| CLI tool | ✅ |
| 51 companion NodeSet types | ✅ |
| PHP 8.2 / 8.3 / 8.4 / 8.5 | ✅ |
| Zero runtime deps (except ext-openssl) | ✅ |
| 1290+ tests, 99%+ coverage | ✅ |
Conclusion
php-opcua/opcua-client fills a real gap: industrial systems speak OPC UA, and PHP has always been left out of that conversation. This library brings the full protocol stack to PHP with production-grade security, a clean API, a MockClient for testing, and a growing ecosystem of companion packages.
If you're building anything in the IIoT or Industry 4.0 space with PHP — connecting Laravel to factory floor equipment, pulling data from historians, or monitoring production lines — this library is worth a look.
Links:
- 🌐 Website: php-opcua.com
- 📦 GitHub: github.com/php-opcua/opcua-client
- 📦 Packagist: packagist.org/packages/php-opcua/opcua-client
- 🛠️ CLI: github.com/php-opcua/opcua-cli
- 🏭 Laravel: github.com/php-opcua/laravel-opcua
Top comments (0)