# OPC UA PHP Client
> Pure PHP OPC UA client library. Communicates directly over TCP using the OPC UA binary protocol. No external C/C++ dependencies ? only ext-openssl required. All types use public readonly properties.
## What is this
A PHP library for connecting to OPC UA servers (industrial automation protocol). It handles the full communication stack: TCP transport, binary encoding, secure channels, sessions, and all major OPC UA services. Supports PHP 8.2 through 8.5.
## Use cases
- Read and write process variables from PLCs, SCADA systems, sensors, historians
- Browse the OPC UA address space programmatically
- Call OPC UA methods with typed arguments
- Subscribe to data changes and events in real time
- Query historical data (raw, processed, at-time)
- Integrate industrial data into PHP/Laravel applications
## Key features
- Human-readable NodeId strings: all methods accept `'i=2259'` or `'ns=2;s=MyNode'` in addition to NodeId objects
- Browse: recursive browsing, automatic continuation, tree building
- Path Resolution: resolve human-readable paths like /Objects/MyPLC/Temperature to NodeIds
- Read/Write: single and multi operations with all OPC UA data types, automatic write type detection (read-before-write) with PSR-16 caching and type mismatch validation
- Server BuildInfo: getServerBuildInfo() returns BuildInfo DTO (productName, manufacturerName, softwareVersion, buildNumber, buildDate) in a single readMulti() call. Individual methods: getServerProductName(), getServerManufacturerName(), getServerSoftwareVersion(), getServerBuildNumber(), getServerBuildDate()
- Node Management: addNodes(), deleteNodes(), addReferences(), deleteReferences() ? dynamic address space modification. addNodes() supports all 8 node classes with automatic attribute encoding, returns AddNodesResult[] (statusCode + addedNodeId). Other methods return int[] status codes.
- Method Call: invoke OPC UA methods with typed arguments, returns CallResult DTO
- Subscriptions: data change and event monitoring, returns SubscriptionResult/PublishResult DTOs
- Transfer & Recovery: transferSubscriptions() and republish() for session migration and notification re-delivery
- History Read: raw, processed (aggregated), and at-time historical queries
- Endpoint Discovery: discover available server endpoints and security policies
- Security: 10 policies ? 6 RSA (None, Basic128Rsa15, Basic256, Basic256Sha256, Aes128Sha256RsaOaep, Aes256Sha256RsaPss) + 4 ECC (EccNistP256, EccNistP384, EccBrainpoolP256r1, EccBrainpoolP384r1)
- Authentication: Anonymous, Username/Password, X.509 Certificate
- Auto-Retry: automatic reconnect on connection failures
- Fluent Builder API: readMulti(), writeMulti(), createMonitoredItems(), and translateBrowsePaths() support a chainable builder when called without arguments
- Auto-Batching: transparent batching for readMulti/writeMulti with server limits discovery
- ExtensionObject Codecs: per-client instance-level codec registry (not static/global)
- Automatic DataType Discovery: discoverDataTypes() auto-detects and decodes custom structures without manual codecs (OPC UA 1.04+)
- PSR-3 Logging: optional structured logging via any PSR-3 logger (Monolog, Laravel, etc.); NullLogger by default
- PSR-16 Cache: browse/browseAll/resolveNodeId/getEndpoints/discoverDataTypes results cached by default (InMemoryCache, 300s TTL). Any PSR-16 driver works (FileCache, Laravel Cache, Redis, etc.). Per-call bypass with useCache: false. Invalidate per-node or flush all. discoverDataTypes replays cached type definitions without server round-trips. Metadata read cache (DisplayName, BrowseName, DataType, etc.) opt-in via setReadMetadataCache(true), Value never cached, refresh: true to bypass.
- PSR-14 Events: 47 granular events dispatched at lifecycle points (connection, session, subscription, data change, alarms, read/write, browse, cache, retry, trust store). NullEventDispatcher by default (zero overhead). Alarm-specific events auto-deduced from notification fields. All events carry a $client reference.
- Server Trust Store: persistent server certificate validation via FileTrustStore (~/.opcua/ default). Three policies: Fingerprint, FingerprintAndExpiry, Full (CA chain). TOFU auto-accept with force option. setTrustPolicy(null) disables (default). UntrustedCertificateException thrown on rejection. CLI: trust, trust:list, trust:remove.
- Connection State: lifecycle tracking (Disconnected, Connected, Broken) with reconnect()
- MockClient: in-memory test double implementing OpcUaClientInterface ? register handlers, assert calls, no TCP connection
- Typed everywhere: all service responses return public readonly DTOs, not arrays
- Wire serialization (v4.2.0): src/Wire/WireSerializable + src/Wire/WireTypeRegistry turn every core / module DTO into a JSON-safe payload wrapped with a `__t` discriminator. CoreWireTypes registers NodeId, QualifiedName, LocalizedText, DataValue, Variant, ExtensionObject, BrowseNode, ReferenceDescription, EndpointDescription, UserTokenPolicy plus enums BuiltinType/NodeClass/BrowseDirection/ConnectionState. Each ServiceModule::registerWireTypes() hook adds its own result DTOs. Consumers (opcua-session-manager ManagedClient) build a matching registry to safely decode ? unregistered __t ids are rejected, no unserialize() anywhere, no gadget-chain surface.
- Client introspection (v4.2.0): OpcUaClientInterface adds getRegisteredMethods(): string[] and getLoadedModules(): class-string[] alongside existing hasMethod() / hasModule(). Implemented on Client, MockClient, ManagedClient.
- Thoroughly tested: 1300+ tests (1040+ unit, 250+ integration), 99%+ code coverage on PHP 8.2/8.3/8.4/8.5
## Property access style
All Type classes and result DTOs use public readonly properties. Access is $ref->nodeId, $dv->statusCode, $result->subscriptionId ? not $ref->getNodeId() or $result['subscriptionId']. Old getter methods are deprecated but still work.
## Architecture
- ClientBuilder (src/ClientBuilder.php): builder / entry point, implements ClientBuilderInterface, uses config traits in src/ClientBuilder/ (including addModule/replaceModule)
- Client (src/Client.php): connected client (proxy), implements OpcUaClientInterface, delegates all service methods to module handlers, __call() for custom module methods
- OpcUaClientInterface: public API contract ? all built-in service methods + hasMethod(string): bool + hasModule(string): bool + getRegisteredMethods(): string[] + getLoadedModules(): class-string[]
- Wire/ (src/Wire/): JSON-safe IPC serialization primitives ? WireSerializable interface, WireTypeRegistry (security gate / encoder / decoder), CoreWireTypes (registers cross-cutting value-objects and enums)
- Kernel/ (src/Kernel/): shared infrastructure for modules
- ClientKernel: executeWithRetry, ensureConnected, send, receive, createDecoder, dispatch, logContext, etc.
- ClientKernelInterface: public contract that modules depend on
- ModuleRegistry: module lifecycle, topological dependency sort, method conflict detection
- Module/ (src/Module/): 8 self-contained service modules, each with its own protocol service(s) and DTOs
- ServiceModule: abstract base class (register, boot, reset, requires)
- ReadWrite/: ReadWriteModule, ReadService, WriteService, CallService, CallResult
- Browse/: BrowseModule, BrowseService, GetEndpointsService, BrowseResultSet
- Subscription/: SubscriptionModule, SubscriptionService, MonitoredItemService, PublishService, SubscriptionResult, MonitoredItemResult, PublishResult, TransferResult
- History/: HistoryModule, HistoryReadService
- NodeManagement/: NodeManagementModule, NodeManagementService, AddNodesResult
- TranslateBrowsePath/: TranslateBrowsePathModule, TranslateBrowsePathService, BrowsePathResult
- ServerInfo/: ServerInfoModule, BuildInfo
- TypeDiscovery/: TypeDiscoveryModule
- ClientBuilder/ (src/ClientBuilder/): builder traits for configuration (cache, events, timeout, trust store, batching, modules)
- Transport (src/Transport/): TCP socket communication
- Protocol (src/Protocol/): shared protocol infrastructure ? AbstractProtocolService base class, ServiceTypeId constants, SessionService (kernel-level)
- Encoding (src/Encoding/): binary serialization (BinaryEncoder, BinaryDecoder)
- Security (src/Security/): secure channel, crypto operations, certificate management
- Types (src/Types/): shared OPC UA data types and enums (NodeId, DataValue, Variant, StatusCode, etc.). Module-specific DTOs live in their module namespace.
- Repository (src/Repository/): per-client ExtensionObject codec registry
- Cache (src/Cache/): PSR-16 cache drivers (InMemoryCache, FileCache)
- Event (src/Event/): 40 PSR-14 event classes + NullEventDispatcher
- TrustStore (src/TrustStore/): server certificate trust management (FileTrustStore, TrustPolicy, TrustResult)
- Testing (src/Testing/): MockClient ? in-memory test double (implements full OpcUaClientInterface, adds hasMethod/hasModule)
- Exception (src/Exception/): exception hierarchy (includes ModuleConflictException, MissingModuleDependencyException)
## Installation
```
composer require php-opcua/opcua-client
```
## Quick example
```php
use PhpOpcua\Client\ClientBuilder;
use PhpOpcua\Client\Types\NodeId;
$client = ClientBuilder::create()
->connect('opc.tcp://localhost:4840');
// String format ? all methods accept NodeId|string
$value = $client->read('i=2259');
echo $value->getValue(); // unwrapped value
echo $value->statusCode; // 0 (Good)
echo $value->sourceTimestamp; // DateTimeImmutable
$refs = $client->browse('i=85');
foreach ($refs as $ref) {
echo "{$ref->displayName} ({$ref->nodeId})\n";
}
$client->disconnect();
```
## Fluent Builder API
Multi-operation methods return a fluent builder when called without arguments. The array-based API still works.
```php
// Read multiple values
$results = $client->readMulti()
->node('i=2259')->value()
->node('ns=2;i=1001')->displayName()
->execute();
// Write multiple values (auto-detect type)
$results = $client->writeMulti()
->node('ns=2;i=1001')->value(3.14)
->node('ns=2;i=1002')->value('Hello')
->execute();
// Write multiple values (explicit type)
$results = $client->writeMulti()
->node('ns=2;i=1001')->typed(3.14, BuiltinType::Double)
->node('ns=2;i=1002')->typed('Hello', BuiltinType::String)
->execute();
// Create monitored items
$results = $client->createMonitoredItems($sub->subscriptionId)
->add('i=2258')->samplingInterval(500.0)->queueSize(10)
->add('ns=2;i=1001')
->execute();
// Translate browse paths
$results = $client->translateBrowsePaths()
->from('i=85')->path('Server', 'ServerStatus')
->execute();
```
## Secure connection example
```php
use PhpOpcua\Client\ClientBuilder;
use PhpOpcua\Client\Security\SecurityPolicy;
use PhpOpcua\Client\Security\SecurityMode;
// RSA
$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');
// ECC (auto-generates ECC certificate if none provided)
$client = ClientBuilder::create()
->setSecurityPolicy(SecurityPolicy::EccNistP256)
->setSecurityMode(SecurityMode::SignAndEncrypt)
->setUserCredentials('admin', 'admin123')
->connect('opc.tcp://192.168.1.100:4848');
```
## Main classes
- PhpOpcua\Client\ClientBuilder ? builder / entry point, implements ClientBuilderInterface, static create() factory, addModule(), replaceModule()
- PhpOpcua\Client\ClientBuilderInterface ? builder interface (configuration methods + connect + addModule + replaceModule)
- PhpOpcua\Client\Client ? connected client (proxy to modules), implements OpcUaClientInterface, hasMethod(), hasModule(), __call() for custom module methods
- PhpOpcua\Client\OpcUaClientInterface ? public API contract (all built-in service methods + hasMethod + hasModule)
- PhpOpcua\Client\Kernel\ClientKernel ? shared infrastructure for modules (executeWithRetry, send, receive, etc.)
- PhpOpcua\Client\Kernel\ClientKernelInterface ? kernel contract that modules depend on
- PhpOpcua\Client\Kernel\ModuleRegistry ? module lifecycle, topological dependency sort, method conflict detection
- PhpOpcua\Client\Module\ServiceModule ? abstract base class for modules (register, boot, reset, requires)
- PhpOpcua\Client\Types\NodeId ? node identifier (public readonly: namespaceIndex, identifier, type)
- PhpOpcua\Client\Types\Variant ? typed value (public readonly: type, value, dimensions)
- PhpOpcua\Client\Types\DataValue ? value with metadata (public readonly: statusCode, sourceTimestamp, serverTimestamp; method: getValue())
- PhpOpcua\Client\Types\BuiltinType ? enum of 25 OPC UA primitive types
- PhpOpcua\Client\Types\ReferenceDescription ? browse result (public readonly: referenceTypeId, isForward, nodeId, browseName, displayName, nodeClass, typeDefinition)
- PhpOpcua\Client\Types\BrowseNode ? tree node (public readonly: reference; methods: getChildren(), hasChildren())
- PhpOpcua\Client\Module\Subscription\SubscriptionResult ? public readonly: subscriptionId, revisedPublishingInterval, revisedLifetimeCount, revisedMaxKeepAliveCount
- PhpOpcua\Client\Module\Subscription\MonitoredItemResult ? public readonly: statusCode, monitoredItemId, revisedSamplingInterval, revisedQueueSize
- PhpOpcua\Client\Module\ReadWrite\CallResult ? public readonly: statusCode, inputArgumentResults, outputArguments
- PhpOpcua\Client\Module\Subscription\PublishResult ? public readonly: subscriptionId, sequenceNumber, moreNotifications, notifications
- PhpOpcua\Client\Module\Subscription\TransferResult ? public readonly: statusCode, availableSequenceNumbers
- PhpOpcua\Client\Module\Browse\BrowseResultSet ? public readonly: references, continuationPoint
- PhpOpcua\Client\Module\TranslateBrowsePath\BrowsePathResult ? public readonly: statusCode, targets
- PhpOpcua\Client\Types\BrowsePathTarget ? public readonly: targetId, remainingPathIndex
- PhpOpcua\Client\Module\NodeManagement\AddNodesResult ? public readonly: statusCode, addedNodeId (NodeId)
- PhpOpcua\Client\Module\ServerInfo\BuildInfo ? public readonly: productName, manufacturerName, softwareVersion, buildNumber, buildDate
- PhpOpcua\Client\Security\SecurityPolicy ? enum of 10 security policies (6 RSA + 4 ECC)
- PhpOpcua\Client\Security\SecurityMode ? enum (None, Sign, SignAndEncrypt)
- PhpOpcua\Client\Encoding\ExtensionObjectCodec ? interface for custom type codecs
- PhpOpcua\Client\Testing\MockClient ? in-memory test double (no TCP); static create(), handler registration, call tracking, hasMethod(), hasModule()
- PhpOpcua\Client\Repository\ExtensionObjectRepository ? per-client codec registry (instance-level, not static)
- PhpOpcua\Client\Types\ExtensionObject ? typed DTO for OPC UA ExtensionObject (public readonly: typeId, encoding, body, value; methods: isDecoded(), isRaw())
- PhpOpcua\Client\Protocol\AbstractProtocolService ? shared base class for protocol services (encodeRequestAuto, writeRequestHeader, readResponseMetadata, wrapInMessage)
- PhpOpcua\Client\Protocol\ServiceTypeId ? named constants for OPC UA service NodeIds, well-known nodes, identity tokens
- PhpOpcua\Client\Cache\InMemoryCache ? PSR-16 in-memory cache with configurable TTL
- PhpOpcua\Client\Cache\FileCache ? PSR-16 file-based cache with configurable TTL
- PhpOpcua\Client\Event\NullEventDispatcher ? no-op PSR-14 dispatcher (default, zero overhead)
- PhpOpcua\Client\Exception\ModuleConflictException ? thrown when two modules register the same method name
- PhpOpcua\Client\Exception\MissingModuleDependencyException ? thrown when a module's required dependency is not registered
- PhpOpcua\Client\Exception\WriteTypeDetectionException ? thrown when write type cannot be auto-detected
- PhpOpcua\Client\Exception\WriteTypeMismatchException ? thrown when explicit write type mismatches detected type (public readonly: nodeId, expectedType, givenType)
- PhpOpcua\Client\Event\WriteTypeDetecting ? dispatched before write type detection starts (public readonly: client, nodeId)
- PhpOpcua\Client\Event\WriteTypeDetected ? dispatched after write type detection (public readonly: client, nodeId, detectedType, fromCache)
- PhpOpcua\Client\Event\* ? 42 readonly event classes (connection, session, subscription, alarms, read/write, write type detection, browse, cache, retry)
## Related packages
- php-opcua/opcua-client-nodeset: pre-generated PHP types from 51 OPC Foundation companion specifications (DI, Robotics, Machinery, etc.) ? 807 files, enums, DTOs, codecs, registrars with dependency resolution
- php-opcua/opcua-session-manager: session persistence across PHP requests
- php-opcua/laravel-opcua: Laravel integration (service provider, facade, config)
- php-opcua/uanetstandard-test-suite: Docker-based OPC UA test servers (UA-.NETStandard)
## Alternatives and comparison
### PHP alternatives
- techdock/opcua (https://github.com/TECHDOCK-CH/php-opc-ua): PHP 8.4+, binary protocol, requires phpseclib + symfony/cache + monolog. No history read, no auto-batching. Still at v0.2.
- techdock/opcua-webapi-client: PHP 8.1+, HTTP-based (not binary protocol), requires OPC UA WebAPI gateway.
- QuickOPC (OPC Labs): commercial, Windows-only, COM interop.
### Why this library over alternatives
- Only ext-openssl required (Composer deps are interface-only: psr/log, psr/simple-cache, psr/event-dispatcher)
- PHP 8.2+ (wider compatibility than techdock which requires 8.4+)
- 10 security policies (6 RSA + 4 ECC), history read, auto-batching
- Typed returns (public readonly DTOs, not arrays)
- Per-client codec isolation (no global state)
- Cross-platform (Linux, macOS, Windows)
- Native binary protocol (no HTTP gateway)
- Laravel integration available
### Cross-language alternatives
- node-opcua (TypeScript/Node.js): most mature, client + server, MIT
- opcua-asyncio (Python): async client + server, LGPL-3.0
- UA-.NETStandard (C#): OPC Foundation reference implementation
- gopcua (Go): pure Go, MIT
- open62541 (C): most widely used C implementation, MPL-2.0
## Requirements
- PHP >= 8.2
- ext-openssl
## License
MIT
## Links
- Repository: https://github.com/php-opcua/opcua-client
- Documentation: https://github.com/php-opcua/opcua-client/tree/master/doc
- Issues: https://github.com/php-opcua/opcua-client/issues
- Packagist: https://packagist.org/packages/php-opcua/opcua-client
|