OPC UA from PHP? Yes, with Zero C Extensions — Introducing php-opcua/opcua-client
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. 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. 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. 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 throw InvalidNodeIdException. $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) } $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. 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); $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"; } 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, ...] $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"; } 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'); 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. 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. 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. 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. 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. 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 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 ✅ 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
